import { createHash, randomBytes } from 'crypto';
import { sql } from '../db.js';
import type { User } from '@wa-ticketing/shared';
import { companyService } from './companyService.js';
import { authService } from './authService.js';
import { sendSmtpMail, isSmtpConfigured } from './emailService.js';
import { auditService } from './auditService.js';

type SignupIntent = 'new' | 'join';

type CollectedState = {
  intent?: SignupIntent;
  /** New company display name (intent=new) */
  newCompanyName?: string;
  /** Existing company id (intent=join) */
  companyId?: string;
  companyAccountCode?: string;
  companyDisplayName?: string;
  email?: string;
  phone?: string;
  displayName?: string;
};

type SessionRow = {
  id: string;
  session_id: string;
  step: string;
  state: CollectedState;
  verification_token_hash: string | null;
  expires_at: Date;
  consumed_at: Date | null;
};

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

function hashToken(plain: string): string {
  return createHash('sha256').update(plain, 'utf8').digest('hex');
}

function maskEmail(email: string): string {
  const [a, b] = email.split('@');
  if (!b) return '***';
  const left = a.length <= 2 ? '*' : `${a[0]}***${a[a.length - 1]}`;
  return `${left}@${b}`;
}

async function emailTaken(email: string): Promise<boolean> {
  const [row] = await sql<[{ n: bigint }]>`
    SELECT COUNT(*)::bigint AS n FROM users WHERE email = ${email.toLowerCase()}
  `;
  return row ? row.n > 0n : false;
}

async function insertSession(): Promise<string> {
  const [row] = await sql<[{ session_id: string }]>`
    INSERT INTO signup_sessions (step, state, expires_at)
    VALUES ('intent', '{}'::jsonb, NOW() + interval '24 hours')
    RETURNING session_id
  `;
  return row.session_id;
}

async function getSession(sessionId: string): Promise<SessionRow | null> {
  const [row] = await sql<[SessionRow]>`
    SELECT id, session_id, step, state, verification_token_hash, expires_at, consumed_at
    FROM signup_sessions
    WHERE session_id = ${sessionId}::uuid
  `;
  return row ?? null;
}

/** Merge state + step in one write */
async function saveStep(sessionId: string, step: string, state: CollectedState): Promise<void> {
  await sql`
    UPDATE signup_sessions SET step = ${step}, state = ${sql.json(state)}
    WHERE session_id = ${sessionId}::uuid
  `;
}

function parseIntent(text: string): SignupIntent | null {
  const t = text.trim().toLowerCase();
  if (/^(new|register|create|company\s*new)/i.test(t) || t.includes('new company')) return 'new';
  if (/^(join|existing|invite)/i.test(t) || t.includes('join')) return 'join';
  if (t === 'n') return 'new';
  if (t === 'j') return 'join';
  return null;
}

function isAffirmative(text: string): boolean {
  const t = text.trim().toLowerCase();
  return /^(y|yes|ok|confirm|sure|please)/i.test(t);
}

function isNegative(text: string): boolean {
  const t = text.trim().toLowerCase();
  return /^(n|no|cancel|stop)/i.test(t);
}

const WELCOME = [
  'Welcome to Silicon Support.',
  'I can help you register. Are you creating a **new company** account, or **joining** an existing one? Reply with **new** or **join**.',
];

export const signupService = {
  async processChat(opts: {
    sessionId: string | null;
    message: string;
    remoteIp: string;
  }): Promise<{ sessionId: string; messages: string[] }> {
    let { sessionId } = opts;
    let text = opts.message.trim();

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

    const session = await getSession(sessionId);
    if (!session) {
      return { sessionId: sessionId!, messages: ['Session expired or invalid. Please refresh and try Sign up again.'] };
    }
    if (session.consumed_at) {
      return { sessionId, messages: ['This signup is already complete. You can sign in on the login page.'] };
    }
    if (new Date(session.expires_at) < new Date()) {
      return { sessionId, messages: ['This signup session has expired. Close the chat and open Sign up again.'] };
    }

    if (session.step === 'emailed') {
      return {
        sessionId,
        messages: [
          'We already sent a verification link to your email. Open that link to choose your password and finish.',
          'If you need to start over, close this window and click Sign up again.',
        ],
      };
    }

    const state: CollectedState = { ...(session.state || {}) };
    const messages: string[] = [];

    switch (session.step) {
      case 'intent': {
        const intent = parseIntent(text);
        if (!intent) {
          messages.push('Please reply **new** to register a new company, or **join** if your company already uses Silicon Support.');
          break;
        }
        state.intent = intent;
        if (intent === 'new') {
          await saveStep(sessionId, 'company_new', state);
          messages.push('What is your **company name** (as it should appear on the account)?');
        } else {
          await saveStep(sessionId, 'company_join', state);
          messages.push(
            'Enter your **company name** exactly as registered, or your **account code** (starts with C, from your welcome materials).',
          );
        }
        break;
      }
      case 'company_new': {
        const name = text.trim();
        if (name.length < 2) {
          messages.push('Please enter a company name (at least 2 characters).');
          break;
        }
        const norm = companyService.normalizeName(name);
        const exists = await companyService.findByNormalizedName(norm);
        if (exists) {
          messages.push(
            `A company named "${exists.name}" is already registered. If you belong there, type **join** to restart and choose "join", or pick a different name.`,
          );
          break;
        }
        state.newCompanyName = name;
        await saveStep(sessionId, 'email', state);
        messages.push('Thanks. What **email address** should we use for your login?');
        break;
      }
      case 'company_join': {
        const company = await companyService.resolveForJoin(text);
        if (!company) {
          messages.push(
            'We could not find that company. Check the spelling of the company name, or the account code (e.g. C…). Ask your admin if unsure.',
          );
          break;
        }
        state.companyId = company.id;
        state.companyDisplayName = company.name;
        state.companyAccountCode = company.account_code;
        await saveStep(sessionId, 'email', state);
        messages.push(
          `Found **${company.name}** (account code **${company.account_code}**). What **email** should we use for your login?`,
        );
        break;
      }
      case 'email': {
        const em = text.trim().toLowerCase();
        if (!EMAIL_RE.test(em)) {
          messages.push('That does not look like a valid email. Please enter a valid address.');
          break;
        }
        if (await emailTaken(em)) {
          messages.push('That email is already registered. Sign in instead, or use a different email.');
          break;
        }
        state.email = em;
        await saveStep(sessionId, 'phone', state);
        messages.push('What is your **phone number** (include country code if possible)?');
        break;
      }
      case 'phone': {
        const phone = text.trim();
        if (phone.length < 5) {
          messages.push('Please enter a phone number (at least 5 characters).');
          break;
        }
        state.phone = phone;
        await saveStep(sessionId, 'display_name', state);
        messages.push('How should we address you? Enter your **name** (first and last is fine).');
        break;
      }
      case 'display_name': {
        const dn = text.trim();
        if (dn.length < 2) {
          messages.push('Please enter your name (at least 2 characters).');
          break;
        }
        state.displayName = dn;
        await saveStep(sessionId, 'confirm', state);
        const intentLabel = state.intent === 'new' ? `New company: **${state.newCompanyName}**` : `Join: **${state.companyDisplayName}**`;
        messages.push(
          'Please confirm:',
          `${intentLabel}`,
          `Email: **${state.email}**`,
          `Phone: **${state.phone}**`,
          `Name: **${state.displayName}**`,
          'Reply **yes** to send a verification email, or **no** to cancel.',
        );
        break;
      }
      case 'confirm': {
        if (isNegative(text)) {
          await saveStep(sessionId, 'intent', {});
          messages.push('No problem — let’s start over.', ...WELCOME.slice(1));
          break;
        }
        if (!isAffirmative(text)) {
          messages.push('Reply **yes** to confirm and send the verification email, or **no** to cancel.');
          break;
        }
        if (!isSmtpConfigured()) {
          messages.push(
            'Signup email is not configured on this server (SMTP). Please contact support — an administrator must set SMTP_HOST and SMTP_FROM.',
          );
          break;
        }
        const plainToken = randomBytes(32).toString('hex');
        const tokenHash = hashToken(plainToken);
        const expires = new Date(Date.now() + 48 * 60 * 60 * 1000);

        const frontend = (process.env.FRONTEND_URL ?? 'http://localhost:3000').replace(/\/$/, '');
        const link = `${frontend}/signup/complete?token=${encodeURIComponent(plainToken)}`;

        const sent = await sendSmtpMail({
          to: state.email!,
          subject: 'Verify your Silicon Support account',
          text: [
            `Hi ${state.displayName},`,
            '',
            'Use this link to choose your password and activate your account:',
            link,
            '',
            'This link expires in 48 hours.',
            '',
            'If you did not request this, you can ignore this email.',
          ].join('\n'),
        });

        if (!sent.ok) {
          messages.push('We could not send the email right now. Please try again later or contact support.');
          break;
        }

        await sql`
          UPDATE signup_sessions
          SET step = 'emailed',
              verification_token_hash = ${tokenHash},
              expires_at = ${expires}
          WHERE session_id = ${sessionId}::uuid
        `;

        await auditService.log(null, 'system', 'signup.verification_sent', 'signup_session', session.id, {
          email: state.email,
        });

        messages.push(
          `Done — we sent a message to **${maskEmail(state.email!)}**.`,
          'Open the link in that email to set your password and finish registration.',
        );
        break;
      }
      default:
        messages.push('Something went wrong. Close the chat and try Sign up again.');
    }

    return { sessionId, messages };
  },

  async verifyToken(plainToken: string): Promise<{
    ok: boolean;
    email?: string;
    hint?: string;
    error?: string;
  }> {
    if (!plainToken?.trim()) return { ok: false, error: 'token required' };
    const h = hashToken(plainToken.trim());
    const rows = await sql<
      { state: CollectedState; expires_at: Date; consumed_at: Date | null }[]
    >`
      SELECT state, expires_at, consumed_at
      FROM signup_sessions
      WHERE verification_token_hash = ${h}
      LIMIT 1
    `;
    const row = rows[0];
    if (!row) return { ok: false, error: 'invalid or expired link' };
    if (row.consumed_at) return { ok: false, error: 'link already used' };
    if (new Date(row.expires_at) < new Date()) return { ok: false, error: 'link expired' };
    const hint =
      row.state.intent === 'new'
        ? row.state.newCompanyName
        : row.state.companyDisplayName ?? undefined;
    return { ok: true, email: row.state.email, hint };
  },

  async completeSignup(
    plainToken: string,
    password: string,
  ): Promise<{ token: string; user: User } | { error: string; status: number }> {
    if (!password || password.length < 8) {
      return { error: 'Password must be at least 8 characters', status: 400 };
    }
    const h = hashToken(plainToken.trim());
    const rows = await sql<
      { id: string; state: CollectedState; expires_at: Date; consumed_at: Date | null }[]
    >`
      SELECT id, state, expires_at, consumed_at
      FROM signup_sessions
      WHERE verification_token_hash = ${h}
      LIMIT 1
    `;
    const row = rows[0];
    if (!row) return { error: 'invalid or expired link', status: 400 };
    if (row.consumed_at) return { error: 'link already used', status: 400 };
    if (new Date(row.expires_at) < new Date()) return { error: 'link expired', status: 400 };

    const st = row.state;
    if (!st.email || !st.displayName || !st.phone || !st.intent) {
      return { error: 'incomplete signup session', status: 400 };
    }

    if (await emailTaken(st.email)) {
      return { error: 'email already registered', status: 409 };
    }

    let companyId: string;
    if (st.intent === 'new') {
      if (!st.newCompanyName) return { error: 'incomplete signup', status: 400 };
      const norm = companyService.normalizeName(st.newCompanyName);
      const exists = await companyService.findByNormalizedName(norm);
      if (exists) {
        return { error: 'company name was registered in the meantime; contact support', status: 409 };
      }
      const company = await companyService.create(st.newCompanyName);
      companyId = company.id;
    } else {
      if (!st.companyId) return { error: 'incomplete signup', status: 400 };
      const company = await companyService.getById(st.companyId);
      if (!company) return { error: 'company not found', status: 400 };
      companyId = company.id;
    }

    try {
      await authService.createCompanyPortalUser(st.email, password, companyId, st.displayName, st.phone);
    } catch (e: unknown) {
      const err = e as { message?: string };
      return { error: err.message ?? 'could not create user', status: 409 };
    }

    await sql`
      UPDATE signup_sessions SET consumed_at = NOW() WHERE id = ${row.id}
    `;

    await auditService.log(null, 'system', 'signup.completed', 'company', companyId, {
      email: st.email,
    });

    const { token, user } = await authService.login(st.email, password);
    return { token, user };
  },
};
