/*
 * Priority Forge — XMem memory-hub adapter
 *
 * Real implementation against the XMem managed API documented at
 * https://docs.mem.xtrace.ai/api/typescript (npm: @xtraceai/memory).
 * Two-channel writes: structured `memory` for faithful round-trip, plus
 * natural-language `messages` for semantic search.
 *
 * Auth (verified by reading @xtraceai/memory v0.0.1 SDK source):
 *   - Authorization: Bearer <apiKey>
 *   - X-Org-Id: <orgID>     (note: capital O lowercase d-then-id, the
 *                            exact SDK header)
 * Base URL: the SDK hardcodes "https://api.xtrace.ai" and constructs paths
 * like "/v1/memories"; our config uses "https://api.xtrace.ai/v1" + "/memories"
 * which resolves to the same final URL. The `environment` SDK parameter does
 * NOT switch hosts (verified from SDK source) — it's reserved for future use.
 *
 * 2026-05-17 status: docs and SDK are public; API backend is NOT yet deployed
 * — probe against the live host with valid credentials returns Express 404
 * for every route. Adapter is wire-faithful; flip the backend in config when
 * the deploy lands. See docs/PLAN_XMEM_ACTIVATION.md for the full status.
 */

import {
  BlockerEdge,
  ClaimOutcome,
  ClaimResult,
  CreateSharedTaskInput,
  MemoryHub,
  MemoryHubHealth,
  OrchestrationHub,
  ReleaseResult,
  RenewResult,
  CancelResult,
  SharedTask,
  SharedTaskFilter,
  TeamActivityFilter,
  TeamMemoryEvent,
  TeamMemoryEventType,
} from '../types';

interface XMemAdapterOptions {
  baseUrl: string;
  apiKey: string;
  orgID: string;
  environment?: 'staging' | 'production';
  fetchImpl?: typeof fetch;
  timeoutMs?: number;
}

interface AddMemoriesResponse {
  results?: Array<{
    id?: string;
    event?: 'ADD' | 'UPDATE' | 'DELETE' | 'NOOP';
  }>;
}

interface MemoryRecord {
  id?: string;
  user_id?: string;
  agent_id?: string;
  memory?: string;
  metadata?: Record<string, unknown>;
  created_at?: string;
  updated_at?: string;
}

interface MemoryListResponse {
  results?: MemoryRecord[];
  next_cursor?: string | null;
}

interface SearchResponse {
  results?: Array<MemoryRecord & { score?: number }>;
}

function recordToEvent(rec: MemoryRecord): TeamMemoryEvent | null {
  const meta = (rec.metadata ?? {}) as Record<string, unknown>;
  const payloadRaw = typeof rec.memory === 'string' ? rec.memory : '';
  let payload: Record<string, unknown> = {};
  try {
    if (payloadRaw) payload = JSON.parse(payloadRaw) as Record<string, unknown>;
  } catch {
    payload = { raw: payloadRaw };
  }
  const eventType = typeof meta.event_type === 'string' ? meta.event_type : 'task_updated';
  return {
    eventId: typeof meta.event_id === 'string' ? meta.event_id : rec.id ?? '',
    teamId: typeof rec.user_id === 'string' ? rec.user_id : '',
    agentId: typeof rec.agent_id === 'string' ? rec.agent_id : '',
    workspaceId: typeof meta.workspace_id === 'string' ? meta.workspace_id : '',
    eventType: eventType as TeamMemoryEventType,
    timestamp:
      typeof meta.timestamp === 'string'
        ? meta.timestamp
        : rec.created_at ?? new Date().toISOString(),
    payload,
    summary: '',
    metadata: meta as Record<string, string | number | boolean | string[] | null>,
  };
}

export class XMemMemoryHub implements MemoryHub, OrchestrationHub {
  readonly backend = 'xmem' as const;
  // OrchestrationHub stubbed unsupported until xtrace.ai deploys their API
  // (or until we layer claim semantics on top of their event log). See
  // docs/PLAN_XMEM_ACTIVATION.md for the external deploy block status.
  readonly supportsOrchestration = false;

  private baseUrl: string;
  private apiKey: string;
  private orgID: string;
  private environment: 'staging' | 'production';
  private fetchImpl: typeof fetch;
  private timeoutMs: number;

  constructor(opts: XMemAdapterOptions) {
    this.baseUrl = opts.baseUrl.replace(/\/+$/, '');
    this.apiKey = opts.apiKey;
    this.orgID = opts.orgID;
    this.environment = opts.environment ?? 'production';
    this.fetchImpl = opts.fetchImpl ?? fetch;
    this.timeoutMs = opts.timeoutMs ?? 10000;
  }

  private headers(): Record<string, string> {
    // Verified against @xtraceai/memory v0.0.1 SDK source.
    return {
      Authorization: `Bearer ${this.apiKey}`,
      'X-Org-Id': this.orgID,
      'Content-Type': 'application/json',
    };
  }

  private async request<T>(method: string, urlPath: string, body?: unknown): Promise<T> {
    const url = `${this.baseUrl}${urlPath}`;
    const res = await this.fetchImpl(url, {
      method,
      headers: this.headers(),
      body: body !== undefined ? JSON.stringify(body) : undefined,
      signal: AbortSignal.timeout(this.timeoutMs),
    });
    if (!res.ok) {
      const text = await res.text().catch(() => '');
      throw new Error(`XMem ${method} ${urlPath} → HTTP ${res.status}: ${text.slice(0, 240)}`);
    }
    if (res.status === 204) return undefined as unknown as T;
    return (await res.json()) as T;
  }

  async write(event: TeamMemoryEvent): Promise<{ accepted: boolean; remoteId?: string }> {
    const body = {
      user_id: event.teamId,
      agent_id: event.agentId,
      memory: JSON.stringify(event.payload),
      infer: false,
      messages: [{ role: 'system', content: event.summary }],
      metadata: {
        event_type: event.eventType,
        event_id: event.eventId,
        workspace_id: event.workspaceId,
        timestamp: event.timestamp,
        ...event.metadata,
      },
    };
    try {
      const res = await this.request<AddMemoriesResponse>('POST', '/memories', body);
      const remoteId = res.results?.[0]?.id;
      return { accepted: true, remoteId };
    } catch (err) {
      console.error('[memory-hub:xmem] write failed:', err instanceof Error ? err.message : err);
      return { accepted: false };
    }
  }

  async read(filter: TeamActivityFilter): Promise<TeamMemoryEvent[]> {
    const params = new URLSearchParams({ user_id: filter.teamId });
    if (filter.limit) params.set('limit', String(filter.limit));
    try {
      const res = await this.request<MemoryListResponse>('GET', `/memories?${params.toString()}`);
      const events = (res.results ?? [])
        .map(recordToEvent)
        .filter((e): e is TeamMemoryEvent => e !== null);
      return events.filter((ev) => {
        if (filter.sinceTs && ev.timestamp < filter.sinceTs) return false;
        if (filter.agentIds && filter.agentIds.length > 0 && !filter.agentIds.includes(ev.agentId)) return false;
        if (filter.workspaceIds && filter.workspaceIds.length > 0 && !filter.workspaceIds.includes(ev.workspaceId)) return false;
        if (filter.eventTypes && filter.eventTypes.length > 0 && !filter.eventTypes.includes(ev.eventType)) return false;
        return true;
      });
    } catch (err) {
      console.error('[memory-hub:xmem] read failed:', err instanceof Error ? err.message : err);
      return [];
    }
  }

  async search(teamId: string, query: string, limit = 20): Promise<TeamMemoryEvent[]> {
    try {
      const res = await this.request<SearchResponse>('POST', '/memories/search', {
        user_id: teamId,
        query,
        limit,
      });
      return (res.results ?? [])
        .map(recordToEvent)
        .filter((e): e is TeamMemoryEvent => e !== null);
    } catch (err) {
      console.error('[memory-hub:xmem] search failed:', err instanceof Error ? err.message : err);
      return [];
    }
  }

  async blockersFor(teamId: string, agentId: string): Promise<BlockerEdge[]> {
    const events = await this.read({ teamId, eventTypes: ['derived_blocker'] });
    const latest = new Map<string, BlockerEdge>();
    for (const ev of events) {
      const edge = ev.payload as Partial<BlockerEdge>;
      if (
        typeof edge.blockedTaskId !== 'string' ||
        typeof edge.blockingTaskId !== 'string' ||
        typeof edge.blockedAgentId !== 'string' ||
        typeof edge.blockingAgentId !== 'string' ||
        (edge.status !== 'active' && edge.status !== 'cleared')
      ) continue;
      if (edge.blockedAgentId !== agentId) continue;
      const key = `${edge.blockedTaskId}::${edge.blockingTaskId}`;
      if (!latest.has(key)) {
        latest.set(key, {
          blockedTaskId: edge.blockedTaskId,
          blockedAgentId: edge.blockedAgentId,
          blockingTaskId: edge.blockingTaskId,
          blockingAgentId: edge.blockingAgentId,
          status: edge.status,
          observedAt: typeof edge.observedAt === 'string' ? edge.observedAt : ev.timestamp,
        });
      }
    }
    return [...latest.values()].filter((e) => e.status === 'active');
  }

  async health(): Promise<MemoryHubHealth> {
    try {
      // Cheap health probe — GET /memories with a synthetic user_id and limit 1.
      // Anything that returns 2xx OR a documented 4xx (validation, auth) confirms
      // the route is wired. Network errors / 5xx / 404 all signal "not ok."
      const url = `${this.baseUrl}/memories?user_id=__health__&limit=1`;
      const res = await this.fetchImpl(url, {
        method: 'GET',
        headers: this.headers(),
        signal: AbortSignal.timeout(this.timeoutMs),
      });
      if (res.status === 404) {
        return { ok: false, backend: 'xmem', detail: `route not deployed (HTTP 404 on ${this.baseUrl}/memories)` };
      }
      if (res.status >= 500) {
        return { ok: false, backend: 'xmem', detail: `server error HTTP ${res.status}` };
      }
      return { ok: true, backend: 'xmem', detail: `HTTP ${res.status} from ${this.baseUrl}` };
    } catch (err) {
      return { ok: false, backend: 'xmem', detail: err instanceof Error ? err.message : String(err) };
    }
  }

  // ── OrchestrationHub (unsupported pending deploy) ────────────────────
  //
  // xtrace.ai's API doesn't expose atomic claim semantics today. When it
  // does (or when we layer them on top of their event log), implement the
  // methods below against POST /memories + a metadata convention. Until
  // then every method returns the `unsupported_backend` sentinel so
  // host-side bridges can fall back gracefully.

  async create(_input: CreateSharedTaskInput): Promise<SharedTask> {
    throw new Error(
      'xmem backend does not yet support orchestration — switch to pgvector or dryrun, or wait for xtrace.ai deploy',
    );
  }

  async claim(
    _sharedTaskId: string,
    _agentId: string,
    _agentTeamId: string,
    _ttlMs: number,
  ): Promise<ClaimResult> {
    return { ok: false, reason: 'unsupported_backend' };
  }

  async release(
    _sharedTaskId: string,
    _agentId: string,
    _outcome: ClaimOutcome,
  ): Promise<ReleaseResult> {
    return { ok: false, reason: 'unsupported_backend' };
  }

  async renewLease(
    _sharedTaskId: string,
    _agentId: string,
    _ttlMs: number,
  ): Promise<RenewResult> {
    return { ok: false, reason: 'unsupported_backend' };
  }

  async cancel(_sharedTaskId: string, _byAgentId: string): Promise<CancelResult> {
    return { ok: false, reason: 'unsupported_backend' };
  }

  async get(_sharedTaskId: string): Promise<SharedTask | null> {
    return null;
  }

  async list(_filter: SharedTaskFilter): Promise<SharedTask[]> {
    return [];
  }

  async getMyClaims(_teamId: string, _agentId: string): Promise<SharedTask[]> {
    return [];
  }

  async searchSharedTasks(_teamId: string, _query: string, _limit?: number): Promise<SharedTask[]> {
    return [];
  }
}
