import type { FastifyBaseLogger } from 'fastify';
import type { Server as SocketServer } from 'socket.io';
import { emailContactKey } from '@wa-ticketing/shared';
import { sql } from '../db.js';
import { channelService } from './channelService.js';
import { companyService } from './companyService.js';
import { contactService } from './contactService.js';
import { messagingService } from './messagingService.js';
import { talkbackService } from './talkbackService.js';
import { ticketService } from './ticketService.js';
import { auditService } from './auditService.js';
import { notifyInboundAlerts } from './channelAlertService.js';
import { emitMessageNew } from './messageEmitService.js';
import { detectLandingIntent } from './landingChatIntent.js';

export { detectLandingIntent } from './landingChatIntent.js';

type LandingMode =
  | 'welcome'
  | 'ask_name'
  | 'ask_company'
  | 'ask_email'
  | 'support'
  // Legacy modes from password-chat — treated as welcome
  | 'login_email'
  | 'login_password';

type LandingState = {
  ticket_id?: string;
  contact_id?: string;
  display_name?: string;
  company_id?: string;
  company_name?: string;
  email?: string;
  verified?: boolean;
};

type SessionRow = {
  id: string;
  session_id: string;
  mode: LandingMode;
  state: LandingState;
  expires_at: Date;
};

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const WELCOME = [
  'Hi — I\'m the Silicon Support assistant.',
  'I can open a **support ticket** if your **company** and **details** match our records — no password needed here.',
  'Reply **help** to get support, or **request access** if you need to register. Staff can use the sign-in form on the right for the console.',
];

const REQUEST_ACCESS_HINT = [
  'I couldn\'t match that company and email to an active subscription.',
  'Use **Request access** under the sign-in form on the right to register or join your company — that\'s the existing signup flow.',
];

export type LandingChatResult = {
  sessionId: string;
  messages: string[];
  /** Short reference when a ticket was created/used this turn */
  ticketId?: string;
};

/** Idempotent schema bootstrap for existing prod volumes that skip initdb migrations. */
let landingTableReady: Promise<void> | null = null;
function ensureLandingSessionsTable(): Promise<void> {
  if (!landingTableReady) {
    landingTableReady = (async () => {
      await sql`
        CREATE TABLE IF NOT EXISTS landing_sessions (
          id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
          session_id  UUID NOT NULL UNIQUE DEFAULT gen_random_uuid(),
          mode        VARCHAR(32) NOT NULL DEFAULT 'welcome',
          state       JSONB NOT NULL DEFAULT '{}',
          remote_ip   VARCHAR(64),
          expires_at  TIMESTAMPTZ NOT NULL,
          created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
        )
      `;
      // Indexes and grants stay in migrations because changing them requires table ownership.
    })().catch(err => {
      landingTableReady = null;
      throw err;
    });
  }
  return landingTableReady;
}

async function insertSession(remoteIp: string): Promise<string> {
  await ensureLandingSessionsTable();
  const [row] = await sql<[{ session_id: string }]>`
    INSERT INTO landing_sessions (mode, state, remote_ip, expires_at)
    VALUES ('welcome', '{}'::jsonb, ${remoteIp || null}, NOW() + interval '24 hours')
    RETURNING session_id
  `;
  return row.session_id;
}

async function getSession(sessionId: string): Promise<SessionRow | null> {
  await ensureLandingSessionsTable();
  const [row] = await sql<[SessionRow]>`
    SELECT id, session_id, mode, state, expires_at
    FROM landing_sessions
    WHERE session_id = ${sessionId}
  `;
  if (!row) return null;
  return {
    ...row,
    state: (row.state && typeof row.state === 'object' ? row.state : {}) as LandingState,
  };
}

async function saveSession(sessionId: string, mode: LandingMode, state: LandingState): Promise<void> {
  await sql`
    UPDATE landing_sessions
    SET mode = ${mode}, state = ${sql.json(state as never)}
    WHERE session_id = ${sessionId}
  `;
}

function normalizeMode(mode: LandingMode): LandingMode {
  if (mode === 'login_email' || mode === 'login_password') return 'welcome';
  return mode;
}

/** Email must match an active company_user (or email-keyed contact) for the given company. */
async function emailMatchesCompany(
  companyId: string,
  companyName: string,
  emailRaw: string,
): Promise<{ ok: true; companyId: string; companyName: string } | { ok: false }> {
  const email = emailRaw.trim().toLowerCase();
  if (!EMAIL_RE.test(email)) return { ok: false };

  const [user] = await sql<[{ id: string }]>`
    SELECT id FROM users
    WHERE company_id = ${companyId}
      AND role = 'company_user'
      AND is_active = TRUE
      AND lower(email) = ${email}
    LIMIT 1
  `;
  if (user) return { ok: true, companyId, companyName };

  const emailKey = emailContactKey(email);
  const [contact] = await sql<[{ id: string }]>`
    SELECT id FROM contacts
    WHERE company_id = ${companyId}
      AND phone_number = ${emailKey}
    LIMIT 1
  `;
  if (contact) return { ok: true, companyId, companyName };

  return { ok: false };
}

/** Company must exist; email must match a member of that company. */
export async function matchCompanyAndDetails(
  companyRaw: string,
  emailRaw: string,
): Promise<{ ok: true; companyId: string; companyName: string } | { ok: false; reason: 'company' | 'details' }> {
  const company = await companyService.resolveForJoin(companyRaw);
  if (!company) return { ok: false, reason: 'company' };
  const matched = await emailMatchesCompany(company.id, company.name, emailRaw);
  if (!matched.ok) return { ok: false, reason: 'details' };
  return matched;
}

async function runSupportTurn(opts: {
  io: SocketServer;
  log: FastifyBaseLogger;
  sessionId: string;
  state: LandingState;
  inboundText: string;
}): Promise<{ messages: string[]; state: LandingState; ticketId?: string }> {
  const { io, log, sessionId, inboundText } = opts;
  const state = { ...opts.state };

  if (!state.verified || !state.company_id) {
    return {
      messages: [
        'Before I can open a ticket, I need to match your company and email.',
        'Reply **help** to start again, or use **Request access** on the right if you\'re not registered yet.',
      ],
      state,
    };
  }

  const portalChannelId = await companyService.getPortalChannelId();
  const portalChannel = await channelService.getById(portalChannelId);
  if (!portalChannel?.settings) {
    return {
      messages: ['Web chat is not configured yet. Please try again later, or use Request access on the right.'],
      state,
    };
  }

  const contact = await contactService.ensureGuestPortalContact(sessionId);
  state.contact_id = contact.id;

  // Bind guest contact to the matched company (required for AI talkback + ticket ownership).
  if (contact.company_id !== state.company_id) {
    await companyService.linkContactToCompany(contact.id, state.company_id);
  }
  if (state.display_name?.trim()) {
    await sql`
      UPDATE contacts SET display_name = ${state.display_name.trim()}
      WHERE id = ${contact.id}
    `;
  }

  let ticket =
    state.ticket_id != null ? await ticketService.getById(state.ticket_id) : null;
  let ticketJustCreated = false;

  if (ticket && (ticket.contact_id !== contact.id || ticket.status !== 'open')) {
    ticket = null;
    delete state.ticket_id;
  }

  if (!ticket) {
    const open = await ticketService.findOpenTicket(contact.id, portalChannelId);
    if (open) {
      ticket = open;
    } else {
      // Skip scripted onboarding — we already verified name/company/email.
      const intake = {
        step: 'done' as const,
        answers: {
          whoAreYou: state.display_name ?? '',
          company: state.company_name ?? '',
        },
      };
      ticket = await ticketService.createTicket(contact.id, portalChannelId, intake);
      const hydrated = await ticketService.getById(ticket.id);
      ticket = hydrated ?? ticket;
      ticketJustCreated = true;
      io.emit('ticket:created', { ticket });
    }
    state.ticket_id = ticket.id;
  }

  const message = await messagingService.appendMessage(
    ticket.id,
    'inbound',
    inboundText,
    null,
    undefined,
    'portal',
  );
  await auditService.log(null, 'system', 'landing.message', 'message', message.id, {
    ticketId: ticket.id,
    sessionId,
    companyId: state.company_id,
  });
  await emitMessageNew(io, ticket.id, message);

  if (portalChannel.settings) {
    notifyInboundAlerts({
      ticketId: ticket.id,
      channelId: portalChannelId,
      channelName: portalChannel.name,
      fromLabel: contact.phone_number,
      preview: inboundText,
      settings: portalChannel.settings,
    })
      .then(async (res: { flaggedEscalation: boolean }) => {
        if (res.flaggedEscalation) {
          const t2 = await ticketService.getById(ticket!.id);
          if (t2) io.to(`ticket:${ticket!.id}`).emit('ticket:updated', { ticket: t2 });
        }
      })
      .catch((err: unknown) => log.warn({ err }, '[landing] channel alerts failed'));
  }

  const beforeIds = new Set((await messagingService.getMessages(ticket.id)).map(m => m.id));
  const ticketFresh = (await ticketService.getById(ticket.id)) ?? ticket;

  try {
    await talkbackService.handleInbound({
      io,
      log,
      ticket: ticketFresh,
      channel: portalChannel,
      contactPhone: contact.phone_number,
      inboundText,
    });
  } catch (err) {
    log.error({ err, ticketId: ticket.id }, '[landing] talkback failed');
    return {
      messages: [
        `Ticket **#${ticket.id.slice(0, 8)}** is open — thanks, I've logged that for our team.`,
        'Feel free to add more detail here.',
      ],
      state,
      ticketId: ticket.id,
    };
  }

  const outbound = (await messagingService.getMessages(ticket.id))
    .filter(m => m.direction === 'outbound' && !beforeIds.has(m.id))
    .map(m => m.content)
    .filter(Boolean);

  const ref = `Ticket **#${ticket.id.slice(0, 8)}**`;
  if (outbound.length === 0) {
    return {
      messages: [
        ticketJustCreated
          ? `${ref} is open. I've logged your message for our team — add more detail anytime.`
          : `I've logged that on ${ref}. Add more detail anytime.`,
      ],
      state,
      ticketId: ticket.id,
    };
  }

  const messages = ticketJustCreated ? [`${ref} is open.`, ...outbound] : outbound;
  return { messages, state, ticketId: ticket.id };
}

export const landingChatService = {
  async processChat(opts: {
    sessionId: string | null;
    message: string;
    remoteIp: string;
    io: SocketServer;
    log: FastifyBaseLogger;
  }): Promise<LandingChatResult> {
    let { sessionId } = opts;
    const text = opts.message.trim();

    if (!sessionId) {
      sessionId = await insertSession(opts.remoteIp);
      if (!text) {
        return { sessionId, messages: WELCOME };
      }
    }

    const session = await getSession(sessionId);
    if (!session) {
      return {
        sessionId,
        messages: ['Session expired or invalid. Refresh the page to start again.'],
      };
    }
    if (new Date(session.expires_at) < new Date()) {
      return {
        sessionId,
        messages: ['This chat session has expired. Refresh the page to start again.'],
      };
    }

    let mode = normalizeMode(session.mode);
    let state: LandingState = { ...(session.state || {}) };
    // Drop any leftover password-era fields
    delete (state as { login_email?: string }).login_email;

    if (mode === 'welcome') {
      if (!text) {
        return { sessionId, messages: WELCOME };
      }
      const intent = detectLandingIntent(text);
      if (!intent) {
        await saveSession(sessionId, mode, state);
        return {
          sessionId,
          messages: [
            'Please reply **help** to open a support ticket, or **request access** to register.',
            'Staff: use the sign-in form on the right for console access (no passwords in this chat).',
          ],
        };
      }
      if (intent === 'signup') {
        await saveSession(sessionId, 'welcome', state);
        return {
          sessionId,
          messages: [
            'To register or join a company, use **Request access** under the sign-in form on the right.',
            'Or reply **help** if you already have an account and need support.',
          ],
        };
      }
      if (intent === 'console') {
        await saveSession(sessionId, 'welcome', state);
        return {
          sessionId,
          messages: [
            'Console login stays on the **sign-in form on the right** — this chat never asks for a password.',
            'Reply **help** to open a support ticket with your company and email, or **request access** to register.',
          ],
        };
      }
      // support → collect identity first
      mode = 'ask_name';
      await saveSession(sessionId, mode, state);
      return {
        sessionId,
        messages: ['Sure — what\'s your **full name**?'],
      };
    }

    if (mode === 'ask_name') {
      if (!text || text.length < 2) {
        await saveSession(sessionId, mode, state);
        return { sessionId, messages: ['Please enter your full name.'] };
      }
      if (detectLandingIntent(text) === 'signup') {
        await saveSession(sessionId, 'welcome', state);
        return { sessionId, messages: REQUEST_ACCESS_HINT };
      }
      state.display_name = text.trim();
      mode = 'ask_company';
      await saveSession(sessionId, mode, state);
      return {
        sessionId,
        messages: ['Thanks. What\'s your **company name** (or account code)?'],
      };
    }

    if (mode === 'ask_company') {
      if (!text) {
        await saveSession(sessionId, mode, state);
        return { sessionId, messages: ['Please enter your company name or account code.'] };
      }
      if (detectLandingIntent(text) === 'signup') {
        await saveSession(sessionId, 'welcome', state);
        return { sessionId, messages: REQUEST_ACCESS_HINT };
      }
      const company = await companyService.resolveForJoin(text);
      if (!company) {
        await saveSession(sessionId, 'welcome', {
          display_name: state.display_name,
        });
        return {
          sessionId,
          messages: [
            'I couldn\'t find that company on our platform.',
            ...REQUEST_ACCESS_HINT,
          ],
        };
      }
      state.company_id = company.id;
      state.company_name = company.name;
      mode = 'ask_email';
      await saveSession(sessionId, mode, state);
      return {
        sessionId,
        messages: [
          `Found **${company.name}**. What **work email** is registered for you there?`,
        ],
      };
    }

    if (mode === 'ask_email') {
      if (!text) {
        await saveSession(sessionId, mode, state);
        return { sessionId, messages: ['Please enter your work email.'] };
      }
      if (detectLandingIntent(text) === 'signup') {
        await saveSession(sessionId, 'welcome', state);
        return { sessionId, messages: REQUEST_ACCESS_HINT };
      }
      if (!EMAIL_RE.test(text)) {
        await saveSession(sessionId, mode, state);
        return {
          sessionId,
          messages: ['That doesn\'t look like an email. Please enter your work email (e.g. you@company.com).'],
        };
      }
      if (!state.company_id || !state.company_name) {
        mode = 'ask_company';
        await saveSession(sessionId, mode, state);
        return { sessionId, messages: ['Let\'s confirm your company again — name or account code?'] };
      }

      const matchById = await emailMatchesCompany(state.company_id, state.company_name, text);

      if (!matchById.ok) {
        await saveSession(sessionId, 'welcome', {
          display_name: state.display_name,
        });
        return {
          sessionId,
          messages: [
            'That email isn\'t linked to this company\'s subscription.',
            ...REQUEST_ACCESS_HINT,
          ],
        };
      }

      state.email = text.trim().toLowerCase();
      state.company_id = matchById.companyId;
      state.company_name = matchById.companyName;
      state.verified = true;
      mode = 'support';
      await saveSession(sessionId, mode, state);
      return {
        sessionId,
        messages: [
          `Thanks, **${state.display_name}**. You're matched to **${state.company_name}**.`,
          'Describe your issue and I\'ll open a support ticket (no password required).',
        ],
      };
    }

    // support mode — ticket + talkback only
    if (mode === 'support') {
      if (detectLandingIntent(text) === 'signup' && text.length < 40) {
        await saveSession(sessionId, mode, state);
        return {
          sessionId,
          messages: [
            'Use **Request access** under the sign-in form on the right to register.',
            'You can keep describing your issue here if you still need help on this ticket.',
          ],
        };
      }
      if (detectLandingIntent(text) === 'console' && text.length < 40) {
        await saveSession(sessionId, mode, state);
        return {
          sessionId,
          messages: [
            'Use the **sign-in form on the right** for console access — this chat only opens tickets.',
          ],
        };
      }

      const result = await runSupportTurn({
        io: opts.io,
        log: opts.log,
        sessionId,
        state,
        inboundText: text || 'Hello',
      });
      await saveSession(sessionId, 'support', result.state);
      return {
        sessionId,
        messages: result.messages,
        ticketId: result.ticketId,
      };
    }

    await saveSession(sessionId, 'welcome', state);
    return { sessionId, messages: WELCOME };
  },
};
