import crypto from 'crypto';
import { prisma } from '../db/client';
import { getRedis, REDIS_KEYS } from '../redis';
import { notifyUserMessage } from './telegram';
import { injectAgentMessage } from './zowieWsInject';

export interface UserMessageInput {
  sessionId?: string;
  visitorUuid?: string;
  zowieSessionKey?: string;
  pageUrl?: string;
  text: string;
  conversationId?: string;
}

function dedupHash(text: string): string {
  return crypto.createHash('sha1').update(text.trim().toLowerCase()).digest('hex').slice(0, 16);
}

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

/** Resolve an open support session across Zowie conversation + tracking ids and visitor cookie. */
export async function findOpenSupportSession(input: {
  conversationId?: string;
  zowieSessionKey?: string;
  visitorUuid?: string;
}) {
  const keys = new Set<string>();
  for (const raw of [input.conversationId, input.zowieSessionKey]) {
    if (!raw) continue;
    for (const variant of zowieKeyVariants(raw)) keys.add(variant);
  }

  if (keys.size > 0) {
    const found = await prisma.supportSession.findFirst({
      where: { status: 'open', zowieSessionKey: { in: [...keys] } },
      orderBy: { updatedAt: 'desc' },
    });
    if (found) return found;
  }

  if (input.visitorUuid) {
    return prisma.supportSession.findFirst({
      where: { visitorUuid: input.visitorUuid, status: 'open' },
      orderBy: { updatedAt: 'desc' },
    });
  }

  return null;
}

export async function getOrCreateSession(input: {
  sessionId?: string;
  visitorUuid?: string;
  zowieSessionKey?: string;
  pageUrl?: string;
}): Promise<{ id: string }> {
  if (input.sessionId) {
    const existing = await prisma.supportSession.findUnique({
      where: { id: input.sessionId },
    });
    if (existing) {
      if (input.pageUrl && input.pageUrl !== existing.pageUrl) {
        await prisma.supportSession.update({
          where: { id: existing.id },
          data: { pageUrl: input.pageUrl },
        });
      }
      return { id: existing.id };
    }
  }

  const found = await findOpenSupportSession({
    zowieSessionKey: input.zowieSessionKey,
    visitorUuid: input.visitorUuid,
  });
  if (found) {
    await prisma.supportSession.update({
      where: { id: found.id },
      data: {
        pageUrl: input.pageUrl ?? found.pageUrl,
        zowieSessionKey: input.zowieSessionKey ?? found.zowieSessionKey,
        visitorUuid: input.visitorUuid ?? found.visitorUuid,
      },
    });
    return { id: found.id };
  }

  const created = await prisma.supportSession.create({
    data: {
      visitorUuid: input.visitorUuid,
      zowieSessionKey: input.zowieSessionKey,
      pageUrl: input.pageUrl,
    },
  });
  return { id: created.id };
}

export async function appendUserMessage(input: UserMessageInput): Promise<{ sessionId: string; messageId: string }> {
  const text = input.text.trim();
  if (!text) {
    throw new Error('Empty message');
  }

  const session = await getOrCreateSession(input);
  const redis = getRedis();
  const hash = dedupHash(text);
  const dedupKey = REDIS_KEYS.supportDedup(session.id, hash);
  const isNew = await redis.set(dedupKey, '1', 'EX', 120, 'NX');
  if (!isNew) {
    return { sessionId: session.id, messageId: '' };
  }

  const message = await prisma.supportMessage.create({
    data: {
      sessionId: session.id,
      role: 'user',
      text,
    },
  });

  await prisma.supportSession.update({
    where: { id: session.id },
    data: { updatedAt: new Date() },
  });

  const fullSession = await prisma.supportSession.findUniqueOrThrow({
    where: { id: session.id },
  });
  await notifyUserMessage(fullSession, message);

  return { sessionId: session.id, messageId: message.id };
}

export async function appendAgentMessage(
  sessionId: string,
  text: string,
): Promise<{ messageId: string }> {
  const trimmed = text.trim();
  if (!trimmed) {
    throw new Error('Empty message');
  }

  const message = await prisma.supportMessage.create({
    data: {
      sessionId,
      role: 'agent',
      text: trimmed,
    },
  });

  await prisma.supportSession.update({
    where: { id: sessionId },
    data: { updatedAt: new Date() },
  });

  const redis = getRedis();
  const payload = JSON.stringify({
    id: message.id,
    role: 'agent',
    text: trimmed,
    createdAt: message.createdAt.toISOString(),
  });
  await redis.publish(REDIS_KEYS.supportSessionChannel(sessionId), payload);

  const session = await prisma.supportSession.findUnique({ where: { id: sessionId } });
  if (session?.zowieSessionKey) {
    const wsCount = injectAgentMessage(
      session.zowieSessionKey,
      trimmed,
      session.visitorUuid ?? undefined,
    );
    if (wsCount > 0) {
      console.log(`[support] agent message delivered via WS to ${wsCount} client(s)`);
    } else {
      console.warn(
        `[support] agent message saved but no active chat WS for conversation ${session.zowieSessionKey.slice(0, 8)}`,
      );
    }
  }

  return { messageId: message.id };
}

export async function resolveSessionId(input: {
  sessionId?: string;
  zowieSessionKey?: string;
  visitorUuid?: string;
}): Promise<string | null> {
  if (input.sessionId) {
    const found = await prisma.supportSession.findUnique({ where: { id: input.sessionId } });
    if (found) return found.id;
  }

  const found = await findOpenSupportSession({
    zowieSessionKey: input.zowieSessionKey,
    visitorUuid: input.visitorUuid,
  });
  return found?.id ?? null;
}

export async function getHistory(sessionId: string) {
  return prisma.supportMessage.findMany({
    where: { sessionId },
    orderBy: { createdAt: 'asc' },
  });
}

export async function getSessionWithMessages(sessionId: string) {
  return prisma.supportSession.findUnique({
    where: { id: sessionId },
    include: { messages: { orderBy: { createdAt: 'asc' } } },
  });
}

export async function listOpenSessions(limit = 50) {
  return prisma.supportSession.findMany({
    where: { status: 'open' },
    orderBy: { updatedAt: 'desc' },
    take: limit,
    include: {
      messages: {
        orderBy: { createdAt: 'desc' },
        take: 1,
      },
      _count: { select: { messages: true } },
    },
  });
}

/** Recursively extract user-visible text from Zowie core/rest payloads. */
export function extractUserTextFromBody(body: unknown): string | null {
  if (body == null) return null;
  if (typeof body === 'string') {
    const t = body.trim();
    return t.length >= 1 && t.length <= 4000 ? t : null;
  }
  if (typeof body !== 'object') return null;

  const obj = body as Record<string, unknown>;
  const preferredKeys = [
    'text',
    'content',
    'message',
    'body',
    'userMessage',
    'userText',
    'input',
  ];
  for (const key of preferredKeys) {
    const val = obj[key];
    if (typeof val === 'string' && val.trim()) {
      return val.trim();
    }
  }

  if (obj.message && typeof obj.message === 'object') {
    const nested = extractUserTextFromBody(obj.message);
    if (nested) return nested;
  }
  if (obj.payload && typeof obj.payload === 'object') {
    const nested = extractUserTextFromBody(obj.payload);
    if (nested) return nested;
  }

  for (const val of Object.values(obj)) {
    if (val && typeof val === 'object') {
      const nested = extractUserTextFromBody(val);
      if (nested) return nested;
    }
  }
  return null;
}

export function looksLikeUserMessageRequest(body: unknown): boolean {
  if (!body || typeof body !== 'object') return false;
  const s = JSON.stringify(body).toLowerCase();
  return (
    s.includes('sendmessage') ||
    s.includes('user_message') ||
    s.includes('usermessage') ||
    (s.includes('"message"') && (s.includes('"text"') || s.includes('"content"')))
  );
}

/** Extract user text from Zowie GraphQL SendText / SendButton mutations. */
export function extractGraphqlUserText(
  body: unknown,
): { text: string; conversationId?: string } | null {
  if (!body || typeof body !== 'object') return null;
  const obj = body as { query?: string; variables?: Record<string, unknown> };
  if (!obj.query || typeof obj.query !== 'string') return null;

  const q = obj.query.toLowerCase();
  if (!q.includes('sendtext') && !q.includes('sendbutton')) return null;

  const vars = obj.variables;
  if (!vars || typeof vars !== 'object') return null;

  const text =
    (typeof vars.text === 'string' ? vars.text : '') ||
    (typeof vars.label === 'string' ? vars.label : '') ||
    (typeof vars.title === 'string' ? vars.title : '');
  const trimmed = text.trim();
  if (!trimmed) return null;

  return {
    text: trimmed,
    conversationId:
      typeof vars.conversationId === 'string' ? vars.conversationId : undefined,
  };
}
