import { createHash, randomBytes, randomUUID } from 'node:crypto';
import { Router, type Response } from 'express';
import { z } from 'zod';
import { requireAdmin, requireAuth, type AuthenticatedRequest } from './auth';
import { pool } from './db';
import { contactFingerprintKey, decryptSensitive, encryptSensitive } from './privacy';
import { config } from './config';
import { MINIMUM_COMMON_CONTACTS } from './guardianContacts';
import { autoAssignGuardiansForCandidate } from './autoGuardianSelection';

export const guardianRouter = Router();
const digest = (value: string): string => createHash('sha256').update(value).digest('hex');
const representativeLink = (token: string): string => {
  const entry = new URL(config.representativeEntryUrl);
  entry.hash = new URLSearchParams({ token }).toString(); return entry.toString();
};

export const cleanupExpiredGuardianContactData = async (): Promise<void> => {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    await client.query(
      `UPDATE guardian_invitations SET status='revoked', completed_at=NOW()
       WHERE status IN ('pending','registered') AND expires_at<=NOW()`,
    );
    await client.query(
      `DELETE FROM guardian_contact_hashes h USING guardian_invitations i
       WHERE h.invitation_id=i.id AND (i.expires_at<=NOW() OR i.status NOT IN ('pending','registered'))`,
    );
    await client.query(
      `UPDATE guardian_qr_challenges SET status='expired'
       WHERE (status='issued' AND expires_at<=NOW())
          OR (status='claimed' AND claim_expires_at<=NOW())`,
    );
    await client.query(
      `DELETE FROM guardian_qr_challenges
       WHERE status IN ('expired','consumed') AND issued_at<NOW()-INTERVAL '1 hour'`,
    );
    await client.query(
      `UPDATE representative_access_codes SET status='expired'
       WHERE status='issued' AND expires_at<=NOW()`,
    );
    await client.query(
      `DELETE FROM representative_access_codes
       WHERE status IN ('expired','consumed') AND issued_at<NOW()-INTERVAL '1 hour'`,
    );
    await client.query(
      `UPDATE guardian_representative_invitations
       SET status='revoked',link_token_hash=NULL,link_token_encrypted=NULL
       WHERE status='pending' AND expires_at IS NOT NULL AND expires_at<=NOW()`,
    );
    await client.query('COMMIT');
  } catch (error) {
    await client.query('ROLLBACK');
    throw error;
  } finally { client.release(); }
};

const candidateOnly = (request: AuthenticatedRequest, response: Response): boolean => {
  if (request.authUser?.role === 'user' && request.authUser.accountType === 'candidate') return true;
  response.status(403).json({ error: 'Candidate account required' }); return false;
};

const invitationFields = `i.id, i.status, i.common_contact_count AS "commonContactCount",
  i.candidate_synced_at AS "candidateSyncedAt", i.guardian_synced_at AS "guardianSyncedAt",
  i.created_at AS "createdAt", i.expires_at AS "expiresAt", i.completed_at AS "completedAt",
  i.accepted_by AS "guardianUserId", u.display_name AS "guardianName", u.username,
  i.phone_last_four AS "phoneLastFour", i.admin_note AS "adminNote", i.reviewed_at AS "reviewedAt"`;

const readGuardianState = async (userId: string, accountType: 'candidate' | 'guardian') => {
  const invitations = await pool.query(
    `SELECT ${invitationFields}, c.display_name AS "candidateName"
     FROM guardian_invitations i
     LEFT JOIN users u ON u.id=i.accepted_by
     JOIN users c ON c.id=i.candidate_user_id
     WHERE ${accountType === 'candidate' ? 'i.candidate_user_id=$1' : 'i.accepted_by=$1'}
     ORDER BY i.created_at DESC LIMIT 20`,
    [userId],
  );
  const guardianOf = accountType === 'candidate' ? await pool.query(
    `SELECT ${invitationFields}, c.display_name AS "candidateName"
     FROM guardian_invitations i
     JOIN users u ON u.id=i.accepted_by
     JOIN users c ON c.id=i.candidate_user_id
     WHERE i.accepted_by=$1 AND i.status IN ('registered','verified')
     ORDER BY i.created_at DESC LIMIT 20`,
    [userId],
  ) : { rows: [] as Record<string, unknown>[] };
  const exception = accountType === 'candidate' ? await pool.query(
    `SELECT e.id, e.status, e.phone_last_four AS "phoneLastFour", e.admin_note AS "adminNote",
            e.representative_name AS "representativeName",
            e.representative_user_id AS "representativeUserId",
            r.username AS "representativeUsername", r.is_active AS "representativeActive",
            r.account_type AS "representativeAccountType", i.status AS "invitationStatus",
            i.expires_at AS "invitationExpiresAt",i.link_token_encrypted AS "linkTokenEncrypted",
            e.created_at AS "createdAt", e.reviewed_at AS "reviewedAt",
            e.created_at + INTERVAL '24 hours' AS "estimatedReviewBy"
     FROM guardian_exceptions e
     LEFT JOIN users r ON r.id=e.representative_user_id
     LEFT JOIN guardian_representative_invitations i ON i.exception_id=e.id
     WHERE e.candidate_user_id=$1 ORDER BY e.created_at DESC LIMIT 1`, [userId],
  ) : { rows: [] };
  const rawException = exception.rows[0];
  const publicException = rawException ? (() => {
    const { linkTokenEncrypted, ...item } = rawException;
    const linkAvailable = item.invitationStatus === 'pending'
      && item.invitationExpiresAt && new Date(item.invitationExpiresAt).getTime() > Date.now()
      && linkTokenEncrypted;
    return { ...item, shareUrl: linkAvailable
      ? representativeLink(decryptSensitive(linkTokenEncrypted)) : null };
  })() : null;
  const satisfied = invitations.rows.some((item) => item.status === 'verified')
    || Boolean(publicException?.status === 'approved'
      && publicException.invitationStatus === 'accepted'
      && publicException.representativeActive
      && publicException.representativeAccountType === 'representative');
  return {
    accountType,
    minimumCommonContacts: MINIMUM_COMMON_CONTACTS,
    verificationMayTakeHours: 24,
    satisfied,
    invitations: invitations.rows.map((item) => ({
      ...item,
      fingerprintKey: ['pending', 'registered'].includes(item.status)
        ? contactFingerprintKey(item.id) : null,
    })),
    guardianOf: guardianOf.rows.map((item) => ({
      ...item,
      fingerprintKey: ['pending', 'registered'].includes(item.status as string)
        ? contactFingerprintKey(item.id as string) : null,
    })),
    exception: publicException,
  };
};

guardianRouter.get('/me/guardian-verification', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    if (request.authUser!.role !== 'user' || request.authUser!.accountType === 'representative') {
      response.status(403).json({ error: 'Candidate or guardian account required' }); return;
    }
    response.json({ guardian: await readGuardianState(request.authUser!.id, request.authUser!.accountType) });
  });

guardianRouter.post('/guardian-invitations', requireAuth,
  (_request: AuthenticatedRequest, response) => {
    response.status(410).json({ error: 'Guardian enrollment now requires a rotating QR code' });
  });

guardianRouter.post('/guardian-invitations/:id/revoke', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    if (!candidateOnly(request, response)) return;
    const id = z.uuid().safeParse(request.params.id);
    if (!id.success) { response.status(400).json({ error: 'Invalid invitation ID' }); return; }
    const result = await pool.query(
      `UPDATE guardian_invitations SET status='revoked', completed_at=NOW()
       WHERE id=$1 AND candidate_user_id=$2 AND status IN ('pending','registered') RETURNING id`,
      [id.data, request.authUser!.id],
    );
    if (!result.rowCount) { response.status(404).json({ error: 'Revocable invitation not found' }); return; }
    await pool.query('DELETE FROM guardian_contact_hashes WHERE invitation_id=$1', [id.data]);
    response.json({ revoked: true });
  });

guardianRouter.post('/guardian-invitations/register', (_request, response) => {
  response.status(410).json({ error: 'Guardian enrollment now requires a rotating QR code' });
});

guardianRouter.post('/me/no-guardian', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    if (!candidateOnly(request, response)) return;
    const parsed = z.object({
      representativePhone: z.string().trim().regex(/^\+[1-9]\d{7,14}$/),
      representativeFullName: z.string().trim().min(4).max(120),
    }).safeParse(request.body);
    if (!parsed.success) { response.status(400).json({ error: 'Representative phone and full name are required' }); return; }
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      await client.query('SELECT pg_advisory_xact_lock(hashtext($1))',
        [`guardian-enroll:${request.authUser!.id}`]);
      const previous = await client.query<{ id: string; status: string; invitation_status: string | null }>(
        `SELECT e.id,e.status,i.status AS invitation_status FROM guardian_exceptions e
         LEFT JOIN guardian_representative_invitations i ON i.exception_id=e.id
         WHERE e.candidate_user_id=$1 AND e.status IN ('pending','approved') FOR UPDATE OF e`,
        [request.authUser!.id],
      );
      if (previous.rowCount) {
        await client.query('ROLLBACK'); response.status(409).json({ error: 'A no-guardian review is already active' }); return;
      }
      const linked = await client.query(
        `SELECT 1 FROM guardian_invitations WHERE candidate_user_id=$1 AND status IN ('registered','verified')`,
        [request.authUser!.id],
      );
      if (linked.rowCount) {
        await client.query('ROLLBACK'); response.status(409).json({ error: 'A registered guardian relationship already exists' }); return;
      }
      await client.query(
        `UPDATE guardian_invitations SET status='revoked',completed_at=NOW()
         WHERE candidate_user_id=$1 AND status='pending'`, [request.authUser!.id],
      );
      await client.query(
        `DELETE FROM guardian_contact_hashes h USING guardian_invitations i
         WHERE h.invitation_id=i.id AND i.candidate_user_id=$1 AND i.status='revoked'`,
        [request.authUser!.id],
      );
      const id = randomUUID();
      const invitationId = randomUUID();
      const shareToken = randomBytes(32).toString('base64url');
      await client.query(
        `INSERT INTO guardian_exceptions
           (id,candidate_user_id,phone_encrypted,phone_last_four,representative_name)
         VALUES ($1,$2,$3,$4,$5)`,
        [id, request.authUser!.id, encryptSensitive(parsed.data.representativePhone), parsed.data.representativePhone.slice(-4),
          parsed.data.representativeFullName],
      );
      const created = await client.query<{ expires_at: Date }>(
        `INSERT INTO guardian_representative_invitations
           (id,exception_id,candidate_user_id,representative_user_id,invited_full_name,
            invited_phone_encrypted,invited_phone_last_four,link_token_hash,link_token_encrypted,expires_at)
         VALUES ($1,$2,$3,NULL,$4,$5,$6,$7,$8,NOW()+INTERVAL '15 minutes') RETURNING expires_at`,
        [invitationId, id, request.authUser!.id, parsed.data.representativeFullName,
          encryptSensitive(parsed.data.representativePhone), parsed.data.representativePhone.slice(-4),
          digest(shareToken), encryptSensitive(shareToken)],
      );
      await client.query(
        "INSERT INTO audit_events (user_id,event_type,details) VALUES ($1,'guardian_exception_requested',$2::jsonb)",
        [request.authUser!.id, JSON.stringify({ requestId: id, invitationId })],
      );
      await client.query('COMMIT');
      response.status(201).json({ requestId: id, status: 'pending',
        shareUrl: representativeLink(shareToken), expiresAt: created.rows[0].expires_at });
    } catch (error) {
      await client.query('ROLLBACK');
      if (typeof error === 'object' && error !== null && 'code' in error && error.code === '23505') {
        response.status(409).json({ error: 'A no-guardian review is already active' }); return;
      }
      throw error;
    } finally { client.release(); }
  });

guardianRouter.post('/me/no-guardian/:id/reissue', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    if (!candidateOnly(request, response)) return;
    const id = z.uuid().safeParse(request.params.id);
    if (!id.success) { response.status(400).json({ error: 'Invalid representative request ID' }); return; }
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      const current = await client.query<{
        invitation_id: string; invitation_status: string; representative_name: string;
        phone_encrypted: string; phone_last_four: string;
      }>(
        `SELECT i.id AS invitation_id,i.status AS invitation_status,
                e.representative_name,e.phone_encrypted,e.phone_last_four
         FROM guardian_exceptions e JOIN guardian_representative_invitations i ON i.exception_id=e.id
         WHERE e.id=$1 AND e.candidate_user_id=$2 AND e.status='pending' FOR UPDATE OF e,i`,
        [id.data, request.authUser!.id],
      );
      if (!current.rows[0]) { await client.query('ROLLBACK');
        response.status(404).json({ error: 'Pending representative request not found' }); return; }
      if (current.rows[0].invitation_status === 'accepted') { await client.query('ROLLBACK');
        response.status(409).json({ error: 'Representative account is already enrolled' }); return; }
      const shareToken = randomBytes(32).toString('base64url');
      const renewed = await client.query<{ expires_at: Date }>(
        `UPDATE guardian_representative_invitations
         SET representative_user_id=NULL,status='pending',link_token_hash=$1,link_token_encrypted=$2,
             invited_full_name=$3,invited_phone_encrypted=$4,invited_phone_last_four=$5,
             expires_at=NOW()+INTERVAL '15 minutes',responded_at=NULL,consumed_at=NULL
         WHERE id=$6 RETURNING expires_at`,
        [digest(shareToken), encryptSensitive(shareToken), current.rows[0].representative_name,
          current.rows[0].phone_encrypted, current.rows[0].phone_last_four, current.rows[0].invitation_id],
      );
      await client.query(
        'UPDATE guardian_exceptions SET representative_user_id=NULL WHERE id=$1', [id.data],
      );
      await client.query(
        "INSERT INTO audit_events (user_id,event_type,details) VALUES ($1,'representative_link_reissued',$2::jsonb)",
        [request.authUser!.id, JSON.stringify({ requestId: id.data })],
      );
      await client.query('COMMIT');
      response.json({ shareUrl: representativeLink(shareToken), expiresAt: renewed.rows[0].expires_at });
    } catch (error) { await client.query('ROLLBACK'); throw error; }
    finally { client.release(); }
  });

guardianRouter.post('/me/no-guardian/:id/cancel', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    if (!candidateOnly(request, response)) return;
    const id = z.uuid().safeParse(request.params.id);
    if (!id.success) { response.status(400).json({ error: 'Invalid representative request ID' }); return; }
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      const cancelled = await client.query(
        `UPDATE guardian_exceptions SET status='revoked'
         WHERE id=$1 AND candidate_user_id=$2 AND status='pending' RETURNING id`,
        [id.data, request.authUser!.id],
      );
      if (!cancelled.rowCount) { await client.query('ROLLBACK');
        response.status(404).json({ error: 'Pending representative request not found' }); return; }
      await client.query(
        `UPDATE guardian_representative_invitations
         SET status='revoked',link_token_hash=NULL,link_token_encrypted=NULL WHERE exception_id=$1`, [id.data],
      );
      await client.query(
        "INSERT INTO audit_events (user_id,event_type,details) VALUES ($1,'guardian_exception_cancelled',$2::jsonb)",
        [request.authUser!.id, JSON.stringify({ requestId: id.data })],
      );
      await client.query('COMMIT'); response.json({ cancelled: true });
    } catch (error) { await client.query('ROLLBACK'); throw error; }
    finally { client.release(); }
  });

guardianRouter.get('/admin/guardian-exceptions', requireAuth, requireAdmin,
  async (_request: AuthenticatedRequest, response) => {
    const result = await pool.query(
      `SELECT e.id,e.candidate_user_id AS "candidateUserId",u.display_name AS "candidateName",
              CASE WHEN u.account_type='guardian' THEN NULL ELSE u.email END AS email,
              e.phone_encrypted AS "phoneEncrypted",e.phone_last_four AS "phoneLastFour",
              e.status,e.admin_note AS "adminNote",
              e.representative_name AS "representativeName",
              e.representative_user_id AS "representativeUserId",
              r.username AS "representativeUsername",r.account_type AS "representativeAccountType",
              i.status AS "invitationStatus",i.expires_at AS "invitationExpiresAt",
              (i.representative_user_id IS NOT NULL AND i.invited_phone_encrypted IS NULL
                AND r.is_active) AS "canApproveExistingAccount",
              e.created_at AS "createdAt",e.reviewed_at AS "reviewedAt",
              e.created_at+INTERVAL '24 hours' AS "estimatedReviewBy"
       FROM guardian_exceptions e JOIN users u ON u.id=e.candidate_user_id
       LEFT JOIN users r ON r.id=e.representative_user_id
       LEFT JOIN guardian_representative_invitations i ON i.exception_id=e.id
       ORDER BY CASE e.status WHEN 'pending' THEN 0 ELSE 1 END,e.created_at`,
    );
    response.json({ requests: result.rows.map(({ phoneEncrypted, ...item }) => ({
      ...item, phone: decryptSensitive(phoneEncrypted),
    })) });
  });

guardianRouter.patch('/admin/guardian-exceptions/:id', requireAuth, requireAdmin,
  async (request: AuthenticatedRequest, response) => {
    const id = z.uuid().safeParse(request.params.id);
    const parsed = z.object({
      decision: z.enum(['approved', 'rejected']),
      note: z.string().trim().min(10).max(1000),
      contactConfirmed: z.boolean().optional(),
    }).safeParse(request.body);
    if (!id.success || !parsed.success) { response.status(400).json({ error: 'Invalid review' }); return; }
    if (parsed.data.decision === 'approved' && parsed.data.contactConfirmed !== true) {
      response.status(400).json({ error: 'Administrator must confirm the phone contact before approval' }); return;
    }
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      const pending = await client.query<{
        candidate_user_id: string; representative_user_id: string | null; invitation_id: string;
        invitation_status: string; legacy_account: boolean;
      }>(
        `SELECT e.candidate_user_id,e.representative_user_id,i.id AS invitation_id,
                i.status AS invitation_status,
                (i.representative_user_id IS NOT NULL AND i.invited_phone_encrypted IS NULL) AS legacy_account
         FROM guardian_exceptions e JOIN guardian_representative_invitations i ON i.exception_id=e.id
         WHERE e.id=$1 AND e.status='pending' FOR UPDATE OF e,i`, [id.data],
      );
      if (!pending.rows[0]) {
        await client.query('ROLLBACK'); response.status(404).json({ error: 'Pending review not found' }); return;
      }
      const review = pending.rows[0];
      if (parsed.data.decision === 'approved' && review.invitation_status === 'rejected') {
        await client.query('ROLLBACK'); response.status(409).json({ error: 'Representative rejected the invitation' }); return;
      }
      if (parsed.data.decision === 'approved') {
        if (!review.representative_user_id) {
          await client.query('ROLLBACK'); response.status(409).json({ error: 'Representative account is not enrolled' }); return;
        }
        const representative = await client.query<{ account_type: string; is_active: boolean }>(
          "SELECT account_type,is_active FROM users WHERE id=$1 AND role='user' FOR UPDATE",
          [review.representative_user_id],
        );
        if (!representative.rows[0]?.is_active) {
          await client.query('ROLLBACK'); response.status(409).json({ error: 'Representative account is inactive' }); return;
        }
        if (review.invitation_status !== 'accepted') {
          if (!review.legacy_account) {
            await client.query('ROLLBACK'); response.status(409).json({ error: 'Representative account is not enrolled' }); return;
          }
          if (representative.rows[0].account_type === 'candidate') {
            const used = await client.query<{ used: boolean }>(
              `SELECT EXISTS(SELECT 1 FROM profiles WHERE user_id=$1
                  AND (is_complete OR is_published OR publication_requested))
                OR EXISTS(SELECT 1 FROM engagement_requests
                  WHERE sender_user_id=$1 OR recipient_user_id=$1)
                OR EXISTS(SELECT 1 FROM journeys WHERE first_user_id=$1 OR second_user_id=$1)
                OR EXISTS(SELECT 1 FROM guardian_invitations WHERE candidate_user_id=$1
                  AND status IN ('registered','verified'))
                OR EXISTS(SELECT 1 FROM guardian_exceptions WHERE candidate_user_id=$1
                  AND status IN ('pending','approved')) AS used`, [review.representative_user_id],
            );
            if (used.rows[0].used) {
              await client.query('ROLLBACK');
              response.status(409).json({ error: 'Existing account has member activity and cannot become a representative' }); return;
            }
            await client.query(
              "UPDATE users SET account_type='representative',token_version=token_version+1 WHERE id=$1",
              [review.representative_user_id],
            );
            await client.query('DELETE FROM profiles WHERE user_id=$1', [review.representative_user_id]);
            await client.query('DELETE FROM app_states WHERE user_id=$1', [review.representative_user_id]);
          } else if (representative.rows[0].account_type !== 'representative') {
            await client.query('ROLLBACK'); response.status(409).json({ error: 'Account type cannot represent a guardian' }); return;
          }
          await client.query(
            `UPDATE guardian_representative_invitations
             SET status='accepted',responded_at=NOW(),consumed_at=NOW(),
                 link_token_hash=NULL,link_token_encrypted=NULL WHERE id=$1`, [review.invitation_id],
          );
        } else if (representative.rows[0].account_type !== 'representative') {
          await client.query('ROLLBACK'); response.status(409).json({ error: 'Representative account type is invalid' }); return;
        }
      }
      await client.query(
        `UPDATE guardian_exceptions
         SET status=$1,admin_note=$2,reviewed_by=$3,reviewed_at=NOW(),
             contact_confirmed_at=CASE WHEN $1='approved' THEN NOW() ELSE NULL END
         WHERE id=$4`,
        [parsed.data.decision, parsed.data.note, request.authUser!.id, id.data],
      );
      if (parsed.data.decision === 'rejected') await client.query(
        `UPDATE guardian_representative_invitations
         SET status='revoked',link_token_hash=NULL,link_token_encrypted=NULL WHERE exception_id=$1`,
        [id.data],
      );
      await client.query(
        "INSERT INTO audit_events (user_id,event_type,details) VALUES ($1,'guardian_exception_reviewed',$2::jsonb)",
        [request.authUser!.id, JSON.stringify({ requestId: id.data, decision: parsed.data.decision,
          candidateUserId: review.candidate_user_id,
          representativeUserId: review.representative_user_id,
          contactConfirmed: parsed.data.contactConfirmed === true })],
      );
      await client.query('COMMIT');
      if (parsed.data.decision === 'approved') {
        await autoAssignGuardiansForCandidate(review.candidate_user_id);
      }
    } catch (error) {
      await client.query('ROLLBACK'); throw error;
    } finally { client.release(); }
    response.json({ reviewed: true });
  });
