import type { CommunicationBrief, ObserverOutput } from "./types";

const ENGINE_URL =
  process.env.IKY_ENGINE_URL?.replace(/\/$/, "") ?? "http://127.0.0.1:7777";

const REQUEST_TIMEOUT_MS = 1500;

interface RetrievedItem {
  item_id: number;
  creator_id: number | null;
  title: string | null;
  category: string | null;
  score: number;
}

interface RetrieveResponse {
  items: RetrievedItem[];
}

function hash32(input: string): number {
  let h = 0x811c9dc5;
  for (let i = 0; i < input.length; i++) {
    h ^= input.charCodeAt(i);
    h = Math.imul(h, 0x01000193);
  }
  return (h >>> 0) % 2_147_483_647;
}

function personToUserId(personId: string): number {
  return 100_000 + hash32(`person:${personId}`);
}

function creatorFromDomain(domain: string): number {
  return 10_000 + hash32(`domain:${domain.toLowerCase()}`);
}

function itemIdForTurn(conversationId: string, turn: number): number {
  return 1_000_000 + hash32(`conv:${conversationId}:turn:${turn}`);
}

async function request(path: string, init: RequestInit = {}): Promise<Response> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);

  try {
    return await fetch(`${ENGINE_URL}${path}`, {
      ...init,
      headers: {
        "Content-Type": "application/json",
        ...(init.headers ?? {}),
      },
      signal: controller.signal,
    });
  } finally {
    clearTimeout(timer);
  }
}

async function postJson(path: string, body: unknown): Promise<boolean> {
  try {
    const res = await request(path, {
      method: "POST",
      body: JSON.stringify(body),
    });
    return res.ok;
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    console.error(`[tidal] POST ${path} failed: ${msg}`);
    return false;
  }
}

export async function ensurePersonalizationUser(personId: string): Promise<void> {
  const userId = personToUserId(personId);
  await postJson("/v1/users/upsert", {
    user_id: userId,
    metadata: {
      role: "user",
      source: "iknowyou",
    },
  });
}

export async function ensurePersonalizationSession(
  conversationId: string,
  personId: string
): Promise<void> {
  const userId = personToUserId(personId);
  await postJson("/v1/sessions/start", {
    conversation_id: conversationId,
    user_id: userId,
    agent_id: "aeries",
  });
}

async function upsertAssistantItem(
  conversationId: string,
  turn: number,
  assistantMessage: string,
  domain: string
): Promise<{ itemId: number; creatorId: number }> {
  const itemId = itemIdForTurn(conversationId, turn);
  const creatorId = creatorFromDomain(domain || "general");

  await postJson("/v1/items/upsert", {
    item_id: itemId,
    creator_id: creatorId,
    title: assistantMessage.slice(0, 120),
    category: domain || "general",
  });

  return { itemId, creatorId };
}

function actionFromObserver(output: ObserverOutput): "more" | "less" | "view" {
  const s = output.engagement.sentiment_score;
  if (s >= 0.6 && output.engagement.substantive) return "more";
  if (s <= 0.4 || output.dynamics.redirected) return "less";
  return "view";
}

export async function recordObserverPersonalization(params: {
  personId: string;
  conversationId: string;
  turn: number;
  assistantMessage: string;
  output: ObserverOutput;
}): Promise<void> {
  const { personId, conversationId, turn, assistantMessage, output } = params;
  const userId = personToUserId(personId);
  const domain = output.topic.domain || "general";

  const { itemId, creatorId } = await upsertAssistantItem(
    conversationId,
    turn,
    assistantMessage,
    domain
  );

  const action = actionFromObserver(output);

  await postJson("/v1/feedback", {
    user_id: userId,
    item_id: itemId,
    creator_id: creatorId,
    action,
  });

  // Keep sessions warm with signal-level writes for per-conversation context.
  await postJson("/v1/sessions/signal", {
    conversation_id: conversationId,
    signal_type: action === "more" ? "like" : action === "less" ? "skip" : "view",
    item_id: itemId,
    weight: 1.0,
    annotation: `topic:${output.topic.primary} domain:${domain}`,
  });

  // Optional auxiliary memory (Synap when configured in engine server).
  if (output.engagement.sentiment_score >= 0.75) {
    await postJson("/v1/aux/observation", {
      person_id: userId,
      observation: `Strong positive response to ${domain} / ${output.topic.primary} conversation style`,
    });
  }
}

export async function addPersonalizationHints(
  personId: string,
  brief?: CommunicationBrief
): Promise<CommunicationBrief | undefined> {
  if (!brief) return brief;

  const userId = personToUserId(personId);

  try {
    const res = await request(`/v1/retrieve?user_id=${userId}&limit=5`);
    if (!res.ok) return brief;

    const data = await res.json();
    if (!Array.isArray(data?.items) || !data.items.length) return brief;

    const items = data.items as RetrievedItem[];
    const hintLines = items.map((item) => {
      const category = item.category ?? "general";
      const title = item.title ?? `item-${item.item_id}`;
      return `tidal hint: ${category} style (score ${item.score.toFixed(2)}) via \"${title}\"`;
    });

    return {
      ...brief,
      observations: [...hintLines, ...brief.observations].slice(0, 8),
    };
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    console.error(`[tidal] personalization hints failed: ${msg}`);
    return brief;
  }
}
