import type { FastifyBaseLogger } from 'fastify';
import type postgres from 'postgres';
import type { Server as SocketServer } from 'socket.io';
import type { Channel, ChannelSettings, Ticket, TicketIntake } from '@wa-ticketing/shared';
import { mergeTicketIntake } from '@wa-ticketing/shared';
import { sql } from '../db.js';
import { messagingService } from './messagingService.js';
import { channelService } from './channelService.js';
import { auditService } from './auditService.js';
import { chatCompletionText, isOpenAiConfigured } from './openaiService.js';
import { companyService } from './companyService.js';
import { sendSmtpMail } from './emailService.js';

const KB_MAX = 12_000;

async function sendAutomatedOutbound(params: {
  io: SocketServer;
  ticketId: string;
  channelId: string;
  contactPhone: string;
  text: string;
}): Promise<void> {
  const { io, ticketId, channelId, contactPhone, text } = params;

  // Portal / web chat: deliver in-app only (no Baileys).
  if (contactPhone.startsWith('portal:')) {
    const message = await messagingService.appendMessage(ticketId, 'outbound', text, null, undefined, 'portal');
    await auditService.log(null, 'system', 'message.automated', 'message', message.id, { ticketId });
    io.to(`ticket:${ticketId}`).emit('message:new', { ticketId, message });
    return;
  }

  // Email ticket: SMTP to customer address (suffix after `email:`).
  if (contactPhone.startsWith('email:') && contactPhone.includes('@')) {
    const to = contactPhone.slice(6);
    const sent = await sendSmtpMail({
      to,
      subject: 'Re: Your support request',
      text,
    });
    if (!sent.ok) {
      await auditService.log(null, 'system', 'message.automated_failed', 'message', ticketId, {
        ticketId,
        error: sent.error,
      });
      return;
    }
    const message = await messagingService.appendMessage(ticketId, 'outbound', text, null, undefined, 'email');
    await auditService.log(null, 'system', 'message.automated', 'message', message.id, { ticketId });
    io.to(`ticket:${ticketId}`).emit('message:new', { ticketId, message });
    return;
  }

  const message = await messagingService.appendMessage(ticketId, 'outbound', text, null);

  try {
    const waMessageId = await channelService.sendMessage(channelId, contactPhone, text);
    if (waMessageId) {
      await sql`UPDATE messages SET wa_message_id = ${waMessageId} WHERE id = ${message.id}`;
      message.wa_message_id = waMessageId;
    }
    await auditService.log(null, 'system', 'message.automated', 'message', message.id, { ticketId });
    io.to(`ticket:${ticketId}`).emit('message:new', { ticketId, message });
  } catch (err) {
    await auditService.log(null, 'system', 'message.automated_failed', 'message', message.id, {
      ticketId,
      error: String(err),
    });
  }
}

function nextOnboardingStep(
  settings: ChannelSettings,
  intake: TicketIntake,
  inboundText: string,
): { intake: TicketIntake; outgoing: string | null; advanced: boolean } {
  const t = settings.onboarding.templates;

  if (intake.step === 'idle') {
    const out = t.whoAreYou.trim();
    if (!out) return { intake, outgoing: null, advanced: false };
    return { intake: { step: 'who', answers: { ...intake.answers } }, outgoing: out, advanced: true };
  }

  if (intake.step === 'who') {
    const out = t.company.trim();
    if (!out) return { intake, outgoing: null, advanced: false };
    return {
      intake: { step: 'company', answers: { ...intake.answers, whoAreYou: inboundText.trim() } },
      outgoing: out,
      advanced: true,
    };
  }

  if (intake.step === 'company') {
    const out = t.query.trim();
    if (!out) return { intake, outgoing: null, advanced: false };
    return {
      intake: { step: 'query', answers: { ...intake.answers, company: inboundText.trim() } },
      outgoing: out,
      advanced: true,
    };
  }

  if (intake.step === 'query') {
    const out = t.queryStatus.trim();
    if (!out) return { intake, outgoing: null, advanced: false };
    return {
      intake: { step: 'status', answers: { ...intake.answers, query: inboundText.trim() } },
      outgoing: out,
      advanced: true,
    };
  }

  if (intake.step === 'status') {
    return {
      intake: {
        step: 'done',
        answers: { ...intake.answers, queryStatus: inboundText.trim() },
      },
      outgoing: null,
      advanced: true,
    };
  }

  return { intake, outgoing: null, advanced: false };
}

async function runAiReply(opts: {
  io: SocketServer;
  log: FastifyBaseLogger;
  ticketId: string;
  channelId: string;
  contactPhone: string;
  settings: ChannelSettings;
  intake: TicketIntake;
}): Promise<void> {
  const { io, log, ticketId, channelId, contactPhone, settings, intake } = opts;
  const msgs = await messagingService.getMessages(ticketId);
  const recent = msgs.slice(-20);
  const chatMsgs = recent.map(m => ({
    role: m.direction === 'inbound' ? ('user' as const) : ('assistant' as const),
    content: m.content,
  }));

  const kb = settings.ai.knowledgeBaseText.slice(0, KB_MAX);
  const ctx = JSON.stringify(intake.answers);
  const system = `${settings.ai.systemPrompt}

Knowledge base:
${kb}

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

  try {
    const reply = await chatCompletionText({
      model: settings.ai.model,
      system,
      messages: chatMsgs,
    });
    if (!reply.trim()) return;
    await sendAutomatedOutbound({ io, ticketId, channelId, contactPhone, text: reply });
  } catch (err) {
    log.error({ err, ticketId }, '[talkback] OpenAI reply failed');
    await auditService.log(null, 'system', 'talkback.ai_failed', 'ticket', ticketId, { error: String(err) });
  }
}

export const talkbackService = {
  async handleInbound(opts: {
    io: SocketServer;
    log: FastifyBaseLogger;
    ticket: Ticket;
    channel: Channel;
    contactPhone: string;
    inboundText: string;
  }): Promise<void> {
    const { io, log, ticket, channel, contactPhone, inboundText } = opts;
    // Inbound reached the API only if the worker has a session; DB status can lag behind briefly.
    const settings = channel.settings;
    if (!settings) return;

    // TransactionSql typings omit template-tag call signature; cast via unknown.
    const { outgoing, intakeAfter, sentOnboardingTemplate } = await sql.begin(async txn => {
      const q = txn as unknown as postgres.Sql;
      let outgoingMsg: string | null = null;
      let sentTpl = false;
      let intakeAfterInner: TicketIntake = mergeTicketIntake(ticket.intake);

      const [row] = await q<[Ticket]>`
        SELECT * FROM tickets WHERE id = ${ticket.id} FOR UPDATE
      `;
      if (!row) return { outgoing: null, intakeAfter: intakeAfterInner, sentOnboardingTemplate: false };

      let intake = mergeTicketIntake(row.intake);
      let updated = false;

      if (settings.onboarding.enabled && intake.step !== 'done') {
        const { intake: next, outgoing: out, advanced } = nextOnboardingStep(settings, intake, inboundText);
        if (!advanced) {
          if (intake.step === 'idle') {
            log.warn({ ticketId: ticket.id }, '[talkback] Onboarding template empty; staying on idle');
          }
          return { outgoing: null, intakeAfter: intake, sentOnboardingTemplate: false };
        }
        intake = next;
        outgoingMsg = out;
        sentTpl = Boolean(out && String(out).trim());
        updated = true;
      }

      if (updated) {
        await q`
          UPDATE tickets SET intake = ${q.json(intake as never)}, updated_at = NOW() WHERE id = ${ticket.id}
        `;
      }
      intakeAfterInner = intake;
      return { outgoing: outgoingMsg, intakeAfter: intakeAfterInner, sentOnboardingTemplate: sentTpl };
    });

    const companyName = intakeAfter.answers.company?.trim();
    if (companyName) {
      try {
        const comp = await companyService.upsertByName(companyName);
        await companyService.linkContactToCompany(ticket.contact_id, comp.id);
      } catch (err) {
        log.warn({ err }, '[talkback] company link failed');
      }
    }

    io.to(`ticket:${ticket.id}`).emit('ticket:updated', {
      ticket: { id: ticket.id, intake: intakeAfter },
    });

    const autoReply = outgoing?.trim();
    if (autoReply) {
      await sendAutomatedOutbound({
        io,
        ticketId: ticket.id,
        channelId: channel.id,
        contactPhone,
        text: autoReply,
      });
    }

    const aiEligible =
      intakeAfter.step === 'done' &&
      settings.ai.enabled &&
      (await isOpenAiConfigured());

    if (aiEligible) {
      if (sentOnboardingTemplate) return;
      await runAiReply({
        io,
        log,
        ticketId: ticket.id,
        channelId: channel.id,
        contactPhone,
        settings,
        intake: intakeAfter,
      });
    }
  },
};
