import { prisma } from '../db/client';
import { findOpenSupportSession } from './service';
import {
  buildZowieCustomerMessage,
  filterGraphqlResponseBody,
  withComposerEnabled,
  ZOWIE_CUSTOMER_APP_ID,
  type MessageNode,
} from './zowieFilter';
import { buildStableWelcomeMessage, buildZowieStaffMessage } from './zowieWsInject';

type GraphqlBody = {
  query?: string;
  variables?: Record<string, unknown>;
};

function isMessagesHistoryQuery(body: unknown): boolean {
  if (!body || typeof body !== 'object') return false;
  const q = (body as GraphqlBody).query;
  return typeof q === 'string' && /\bmessages\s*\(/i.test(q);
}

export function extractMessagesQueryConversationId(body: unknown): string | null {
  if (!isMessagesHistoryQuery(body)) return null;
  const vars = (body as GraphqlBody).variables;
  if (!vars || typeof vars !== 'object') return null;
  const id = vars.conversationId;
  return typeof id === 'string' && id.length > 0 ? id : null;
}

function messageEdge(node: MessageNode) {
  return {
    __typename: 'MessageEdge',
    cursor: String(node.time ?? 0),
    node,
  };
}

function stableWelcomeMessage(conversationId: string): MessageNode {
  return buildStableWelcomeMessage(conversationId);
}

function supportMessageToZowie(
  role: string,
  id: string,
  text: string,
  createdAt: Date,
): MessageNode {
  const base =
    role === 'agent'
      ? buildZowieStaffMessage(text, 'proxy-agent')
      : buildZowieCustomerMessage(text);
  return {
    ...base,
    id: role === 'agent' ? `proxy-agent-${id}` : `proxy-user-${id}`,
    time: createdAt.getTime(),
  };
}

function collectMessageEdges(
  value: unknown,
  path: string[] = [],
): { path: string[]; edges: unknown[] } | null {
  if (!value || typeof value !== 'object') return null;
  const obj = value as Record<string, unknown>;

  if (Array.isArray(obj.edges)) {
    const first = obj.edges[0];
    if (
      obj.edges.length === 0 ||
      (first && typeof first === 'object' && 'node' in (first as object))
    ) {
      const node = first ? (first as { node?: { __typename?: string } }).node : undefined;
      if (!first || node?.__typename === 'Message') {
        return { path, edges: obj.edges };
      }
    }
  }

  for (const [key, val] of Object.entries(obj)) {
    if (val && typeof val === 'object') {
      const found = collectMessageEdges(val, [...path, key]);
      if (found) return found;
    }
  }

  return null;
}

function setEdgesAtPath(root: Record<string, unknown>, path: string[], edges: unknown[]): void {
  if (path.length === 0) return;
  let cur: Record<string, unknown> = root;
  for (let i = 0; i < path.length - 1; i++) {
    const key = path[i];
    const next = cur[key];
    if (!next || typeof next !== 'object') return;
    cur = next as Record<string, unknown>;
  }
  const lastKey = path[path.length - 1];
  const target = cur[lastKey];
  if (!target || typeof target !== 'object') return;
  (target as Record<string, unknown>).edges = edges;
}

function nodeFromEdge(edge: unknown): MessageNode | null {
  if (!edge || typeof edge !== 'object') return null;
  const node = (edge as { node?: unknown }).node;
  if (!node || typeof node !== 'object') return null;
  return node as MessageNode;
}

function mergeProxyMessages(
  existingEdges: unknown[],
  proxyNodes: MessageNode[],
): unknown[] {
  const existingIds = new Set<string>();
  const nodes: MessageNode[] = [];

  for (const edge of existingEdges) {
    const node = nodeFromEdge(edge);
    if (!node) continue;
    if (typeof node.id === 'string') {
      if (node.id.startsWith('proxy-')) continue;
      existingIds.add(node.id);
    }
    // Drop upstream AI/staff messages — they disable the composer (userInput !== Enabled).
    if (node.author?.appId !== ZOWIE_CUSTOMER_APP_ID) continue;
    nodes.push(node);
  }

  for (const proxy of proxyNodes) {
    if (proxy.id && existingIds.has(proxy.id)) continue;
    if (proxy.id?.startsWith('proxy-') && nodes.some((n) => n.id === proxy.id)) continue;
    nodes.push(proxy);
  }

  nodes.sort((a, b) => (a.time ?? 0) - (b.time ?? 0));
  nodes.reverse();

  // Zowie enables the send button only when the newest message has userInput === "Enabled".
  if (nodes.length > 0) {
    nodes[0] = withComposerEnabled(nodes[0]);
  }

  return nodes.map(messageEdge);
}

/** Inject welcome + bridged support history into Zowie messages GraphQL responses. */
export async function enrichGraphqlResponseBody(
  raw: Buffer,
  requestBody: unknown,
): Promise<Buffer> {
  const filtered = filterGraphqlResponseBody(raw);
  const conversationId = extractMessagesQueryConversationId(requestBody);
  if (!conversationId) return filtered;

  const text = filtered.toString('utf8').trim();
  if (!text.startsWith('{')) return filtered;

  let parsed: Record<string, unknown>;
  try {
    parsed = JSON.parse(text) as Record<string, unknown>;
  } catch {
    return filtered;
  }

  const located = collectMessageEdges(parsed);
  if (!located || !located.path.includes('messages')) return filtered;

  const proxyNodes: MessageNode[] = [stableWelcomeMessage(conversationId)];

  try {
    const session = await findOpenSupportSession({ conversationId });
    const sessionWithMessages = session
      ? await prisma.supportSession.findUnique({
          where: { id: session.id },
          include: { messages: { orderBy: { createdAt: 'asc' } } },
        })
      : null;

    if (sessionWithMessages) {
      for (const msg of sessionWithMessages.messages) {
        proxyNodes.push(supportMessageToZowie(msg.role, msg.id, msg.text, msg.createdAt));
      }
    }
  } catch (err) {
    console.error('[zowie-history] support session lookup failed:', (err as Error).message);
  }

  const mergedEdges = mergeProxyMessages(located.edges, proxyNodes);
  setEdgesAtPath(parsed, located.path, mergedEdges);

  return Buffer.from(JSON.stringify(parsed), 'utf8');
}
