import { channelAiOfflineMessage, mergeChannelSettings, mergeTicketIntake } from '@wa-ticketing/shared';
import type { ChannelSettings, TicketIntake, Message } from '@wa-ticketing/shared';
import { sql } from '../db.js';
import { messagingService } from './messagingService.js';
import { channelService } from './channelService.js';
import { ticketService } from './ticketService.js';
import { aiAgentService, type AiAgentSecretRow } from './aiAgentService.js';
import { knowledgeModuleService } from './knowledgeModuleService.js';
import { chatCompletionTextWithCredentials } from './openaiService.js';
import { completeChat, isAnyPlatformLlmConfigured, platformChatCompletionText } from './llmProviderService.js';
import { OPENROUTER_DEFAULT_BASE, openRouterRequestHeaders } from './openrouterService.js';
import { parseSuggestionsFromLlm } from './replySuggestionParse.js';

const KB_MAX = 12_000;
const CHANNEL_KB_CAP = 6_000;
const SUGGESTION_COUNT = 3;
const OPENAI_DEFAULT_BASE = 'https://api.openai.com/v1';

export type ReplySuggestionsResult = {
  suggestions: string[];
  available: boolean;
  /** True when suggestions are the channel's AI offline fallback message */
  aiOffline?: boolean;
  reason?: string;
};

function offlineSuggestionsResult(settings: ChannelSettings): ReplySuggestionsResult | null {
  const offline = channelAiOfflineMessage(settings);
  if (!offline) return null;
  return { suggestions: [offline], available: true, aiOffline: true };
}

function messageLine(m: Message): string {
  const who = m.direction === 'inbound' ? 'Customer' : 'Support';
  const content =
    (m.message_type ?? 'text') !== 'text' ? `[${m.message_type}] ${m.content}` : m.content;
  return `${who}: ${content}`;
}

function buildStaffSuggestionSystem(opts: {
  settings: ChannelSettings;
  intake: TicketIntake;
  companyKnowledge: { text: string; titles: string[] } | null;
}): string {
  const { settings, intake, companyKnowledge } = opts;
  const channelKb = settings.ai.knowledgeBaseText.slice(0, CHANNEL_KB_CAP);
  const orgKb = companyKnowledge
    ? companyKnowledge.text.slice(0, Math.max(0, KB_MAX - channelKb.length))
    : '';
  const topicList = companyKnowledge?.titles.join(', ') ?? '';
  const ctx = JSON.stringify(intake.answers);

  const kbGuard = companyKnowledge
    ? `Use only facts from the knowledge sections below. Enabled topics: ${topicList}.`
    : `Use only facts from the channel knowledge below when relevant.`;

  return `You draft reply suggestions for human support staff — not for direct customer delivery.

${kbGuard}
Do not invent facts. Keep each draft concise and professional (WhatsApp-style unless the thread needs more detail).
Vary drafts: one direct, one empathetic, one more detailed when appropriate.

Output format: a JSON object with key "suggestions" containing exactly ${SUGGESTION_COUNT} strings.
Example: {"suggestions":["First draft","Second draft","Third draft"]}
No markdown, no preamble, no plain-text reply — JSON only.

Channel knowledge base:
${channelKb || '(none)'}
${orgKb ? `\nOrganisation knowledge (enabled topics):\n${orgKb}` : ''}

Structured intake (if any):
${ctx}`;
}

function buildSuggestionUserPrompt(messages: Message[]): string {
  const transcript = messages.map(messageLine).join('\n');
  return `Conversation transcript:

${transcript}

Draft ${SUGGESTION_COUNT} reply options the support agent could send next.
Return JSON only: {"suggestions":["...","...","..."]}`;
}

async function resolveLlm(opts: {
  companyId: string | null;
  settings: ChannelSettings;
}): Promise<
  | { ok: true; agent: AiAgentSecretRow | null; model: string }
  | { ok: false; reason: string }
> {
  if (opts.companyId) {
    const agentRow = await aiAgentService.getDefaultAgentForCompany(opts.companyId);
    const runnable = await aiAgentService.isRunnableForCompany(opts.companyId);
    if (!runnable) {
      return { ok: false, reason: 'No AI provider configured (set an agent API key or platform OpenAI/OpenRouter key)' };
    }
    if (agentRow?.api_key?.trim()) {
      return { ok: true, agent: agentRow, model: agentRow.model_id };
    }
    return { ok: true, agent: null, model: opts.settings.ai.model };
  }
  if (!(await isAnyPlatformLlmConfigured())) {
    return { ok: false, reason: 'No AI provider configured' };
  }
  return { ok: true, agent: null, model: opts.settings.ai.model };
}

async function callSuggestionLlm(opts: {
  system: string;
  userPrompt: string;
  agent: AiAgentSecretRow | null;
  model: string;
}): Promise<string> {
  const messages = [{ role: 'user' as const, content: opts.userPrompt }];

  if (opts.agent?.api_key?.trim()) {
    if (
      opts.agent.provider === 'openai' ||
      opts.agent.provider === 'openai_compatible' ||
      opts.agent.provider === 'openrouter'
    ) {
      const base =
        opts.agent.provider === 'openai'
          ? OPENAI_DEFAULT_BASE
          : opts.agent.provider === 'openrouter'
            ? OPENROUTER_DEFAULT_BASE
            : (opts.agent.base_url?.trim() || '').replace(/\/$/, '');
      const isOpenRouter = opts.agent.provider === 'openrouter';
      return chatCompletionTextWithCredentials({
        base,
        apiKey: opts.agent.api_key,
        model: opts.agent.model_id,
        system: opts.system,
        messages,
        maxTokens: 1024,
        responseFormat: 'json_object',
        extraHeaders: isOpenRouter ? openRouterRequestHeaders() : undefined,
        providerLabel: isOpenRouter ? 'OpenRouter' : undefined,
      });
    }
    return completeChat({
      provider: opts.agent.provider,
      apiKey: opts.agent.api_key,
      baseUrl: opts.agent.base_url,
      model: opts.agent.model_id,
      system: opts.system,
      messages,
      maxTokens: 1024,
    });
  }

  return platformChatCompletionText({
    model: opts.model,
    system: opts.system,
    messages,
    maxTokens: 1024,
    responseFormat: 'json_object',
  });
}

export const replySuggestionService = {
  async generate(ticketId: string): Promise<ReplySuggestionsResult> {
    const ticket = await ticketService.getById(ticketId);
    if (!ticket) return { suggestions: [], available: false, reason: 'Ticket not found' };

    if (ticket.status === 'closed') {
      return { suggestions: [], available: false, reason: 'Ticket is closed' };
    }

    const msgs = await messagingService.getMessages(ticketId);
    if (msgs.length === 0) {
      return { suggestions: [], available: false, reason: 'No messages yet' };
    }

    const channel = await channelService.getById(ticket.channel_id);
    if (!channel) return { suggestions: [], available: false, reason: 'Channel not found' };

    const settings = mergeChannelSettings(channel.settings);

    const [contactRow] = await sql<Array<{ company_id: string | null }>>`
      SELECT company_id FROM contacts WHERE id = ${ticket.contact_id}
    `;
    const companyId = contactRow?.company_id ?? null;

    const llm = await resolveLlm({ companyId, settings });
    if (!llm.ok) {
      return offlineSuggestionsResult(settings) ?? { suggestions: [], available: false, reason: llm.reason };
    }

    let companyKnowledge: { text: string; titles: string[] } | null = null;
    if (companyId) {
      const agentRow = await aiAgentService.getDefaultAgentForCompany(companyId);
      const agentKbIds = agentRow ? await aiAgentService.getKnowledgeModuleIds(agentRow.id) : [];
      companyKnowledge =
        agentKbIds.length > 0
          ? await knowledgeModuleService.buildCompanyKnowledgeMarkdownScoped(companyId, agentKbIds)
          : await knowledgeModuleService.buildCompanyKnowledgeMarkdown(companyId);
    }

    const intake = mergeTicketIntake(ticket.intake);
    const system = buildStaffSuggestionSystem({ settings, intake, companyKnowledge });
    const userPrompt = buildSuggestionUserPrompt(msgs.slice(-20));

    try {
      const raw = await callSuggestionLlm({
        system,
        userPrompt,
        agent: llm.agent?.api_key?.trim() ? llm.agent : null,
        model: llm.model,
      });

      const suggestions = parseSuggestionsFromLlm(raw);
      if (suggestions.length === 0) {
        return { suggestions: [], available: false, reason: 'AI returned no suggestions' };
      }
      return { suggestions, available: true };
    } catch (err) {
      return (
        offlineSuggestionsResult(settings) ?? {
          suggestions: [],
          available: false,
          reason: err instanceof Error ? err.message : 'Failed to generate suggestions',
        }
      );
    }
  },
};
