import { randomUUID } from 'crypto';
import type WebSocket from 'ws';

import { config } from '../config';
import { buildZowieCustomerMessage, withComposerEnabled } from './zowieFilter';

/** Zowie chat-core uses legacy graphql-subscriptions (`data`/`start`), not graphql-ws v5 (`next`/`subscribe`). */
export const ZOWIE_WS_DATA_TYPE = 'data';
export const ZOWIE_WS_SUBSCRIBE_TYPES = new Set(['start', 'subscribe', 'subscription_start']);

interface Subscription {
  subscriptionId: string;
  conversationId: string;
  clientWs: WebSocket;
  visitorUuid?: string;
}

const subscriptionsByConversation = new Map<string, Subscription[]>();
const subscriptionsByVisitor = new Map<string, Subscription[]>();

function conversationKeyVariants(id: string): string[] {
  const trimmed = id.trim();
  const noDashes = trimmed.replace(/-/g, '');
  return trimmed === noDashes ? [trimmed] : [trimmed, noDashes];
}

function addSubscription(sub: Subscription): void {
  for (const key of conversationKeyVariants(sub.conversationId)) {
    let list = subscriptionsByConversation.get(key);
    if (!list) {
      list = [];
      subscriptionsByConversation.set(key, list);
    }
    if (!list.includes(sub)) list.push(sub);
  }

  if (sub.visitorUuid) {
    let list = subscriptionsByVisitor.get(sub.visitorUuid);
    if (!list) {
      list = [];
      subscriptionsByVisitor.set(sub.visitorUuid, list);
    }
    if (!list.includes(sub)) list.push(sub);
  }
}

function removeSubscription(sub: Subscription): void {
  for (const key of conversationKeyVariants(sub.conversationId)) {
    const list = subscriptionsByConversation.get(key);
    if (!list) continue;
    const idx = list.indexOf(sub);
    if (idx >= 0) list.splice(idx, 1);
    if (list.length === 0) subscriptionsByConversation.delete(key);
  }

  if (sub.visitorUuid) {
    const list = subscriptionsByVisitor.get(sub.visitorUuid);
    if (!list) return;
    const idx = list.indexOf(sub);
    if (idx >= 0) list.splice(idx, 1);
    if (list.length === 0) subscriptionsByVisitor.delete(sub.visitorUuid);
  }
}

function getSubscriptionsForConversation(conversationId: string): Subscription[] {
  const seen = new Set<Subscription>();
  const results: Subscription[] = [];
  for (const key of conversationKeyVariants(conversationId)) {
    for (const sub of subscriptionsByConversation.get(key) ?? []) {
      if (seen.has(sub)) continue;
      seen.add(sub);
      results.push(sub);
    }
  }
  return results;
}

function getSubscriptionsForVisitor(visitorUuid: string): Subscription[] {
  return subscriptionsByVisitor.get(visitorUuid) ?? [];
}

/** Staff/AI-style message on the left — same author as original Zowie bot (`chatbotize`). */
export function buildZowieStaffMessage(text: string, idPrefix = 'proxy-agent') {
  return withComposerEnabled({
    __typename: 'Message',
    id: `${idPrefix}-${randomUUID()}`,
    time: Date.now(),
    author: {
      __typename: 'Author',
      appId: 'chatbotize',
      userId: config.support.zowieChatbotId,
      metadata: {
        __typename: 'AuthorMetadata',
        firstName: 'Sinsay',
        lastName: '',
        profilePictureUrl: null,
      },
    },
    payload: {
      __typename: 'Text',
      value: text,
      translated: null,
    },
  });
}

export function parseNewMessageSubscribe(
  raw: string | Buffer,
): { subscriptionId: string; conversationId: string } | null {
  try {
    const text = typeof raw === 'string' ? raw : raw.toString('utf8');
    const msg = JSON.parse(text) as {
      type?: string;
      id?: string;
      operationId?: string;
      payload?: { query?: string; variables?: { conversationId?: string } };
    };
    if (!msg.type || !ZOWIE_WS_SUBSCRIBE_TYPES.has(msg.type)) return null;
    const subscriptionId = msg.id ?? msg.operationId;
    if (!msg.payload?.query || !subscriptionId) return null;
    if (!msg.payload.query.includes('newMessage')) return null;
    const conversationId = msg.payload.variables?.conversationId;
    if (!conversationId) return null;
    return { subscriptionId: String(subscriptionId), conversationId: String(conversationId) };
  } catch {
    return null;
  }
}

export function buildWsPushFrame(subscriptionId: string, message: unknown): string {
  return JSON.stringify({
    type: ZOWIE_WS_DATA_TYPE,
    id: subscriptionId,
    payload: { data: { newMessage: message } },
  });
}

export function injectNewMessage(
  conversationId: string,
  message: Record<string, unknown>,
  visitorUuid?: string,
): number {
  const subs = getSubscriptionsForConversation(conversationId);
  const seen = new Set<Subscription>();
  for (const sub of subs) seen.add(sub);
  if (visitorUuid) {
    for (const sub of getSubscriptionsForVisitor(visitorUuid)) seen.add(sub);
  }

  if (seen.size === 0) return 0;

  let sent = 0;
  for (const sub of seen) {
    if (sub.clientWs.readyState !== 1) continue;
    try {
      sub.clientWs.send(buildWsPushFrame(sub.subscriptionId, message));
      sent++;
    } catch (err) {
      console.error('[zowie-ws-inject] send error:', (err as Error).message);
    }
  }
  return sent;
}

/** Stable welcome node for history + WS (always oldest in the thread). */
export function buildStableWelcomeMessage(conversationId: string) {
  const msg = buildZowieStaffMessage(config.support.welcomeMessage, 'proxy-welcome');
  return {
    ...msg,
    id: `proxy-welcome-${conversationId}`,
    time: 1,
  };
}

export function injectWelcomeMessage(conversationId: string): number {
  const text = config.support.welcomeMessage.trim();
  if (!text) return 0;
  return injectNewMessage(conversationId, buildStableWelcomeMessage(conversationId));
}

export function trackClientSubscription(
  clientWs: WebSocket,
  subscriptionId: string,
  conversationId: string,
  visitorUuid?: string,
): void {
  const sub: Subscription = { subscriptionId, conversationId, clientWs, visitorUuid };
  addSubscription(sub);
  console.log(
    `[zowie-ws-inject] subscribed conversation=${conversationId.slice(0, 8)} visitor=${visitorUuid?.slice(0, 8) ?? '—'}`,
  );

  const cleanup = () => removeSubscription(sub);
  clientWs.once('close', cleanup);
  clientWs.once('error', cleanup);
}

/** Push a manager reply into active Zowie GraphQL-WS subscriptions for this conversation. */
export function injectAgentMessage(
  conversationId: string,
  text: string,
  visitorUuid?: string,
): number {
  return injectNewMessage(conversationId, buildZowieStaffMessage(text, 'proxy-agent'), visitorUuid);
}

/** Echo the user's message into the widget (AI upstream is not used). */
export function injectCustomerMessage(
  conversationId: string,
  text: string,
  visitorUuid?: string,
): number {
  return injectNewMessage(conversationId, buildZowieCustomerMessage(text), visitorUuid);
}

export function getActiveSubscriptionCount(conversationId: string): number {
  return getSubscriptionsForConversation(conversationId).length;
}
