import { createHash, randomUUID } from 'node:crypto';
import bcrypt from 'bcryptjs';
import { Router } from 'express';
import { z } from 'zod';
import { createToken, requireAuth, type AuthenticatedRequest } from './auth';
import { config } from './config';
import { pool } from './db';
import { decryptSensitive } from './privacy';
import { generateAvailableUsername } from './usernames';

export const representativeRouter = Router();
const digest = (value: string): string => createHash('sha256').update(value).digest('hex');

const tokenFromPayload = (payload: string): string | null => {
  if (/^[A-Za-z0-9_-]{43}$/.test(payload)) return payload;
  try {
    const entry = new URL(payload);
    const expected = new URL(config.representativeEntryUrl);
    const web = entry.origin === expected.origin && entry.pathname === expected.pathname;
    const native = entry.protocol === 'alatalabak:' && entry.hostname === 'representative-login';
    if (!web && !native) return null;
    const token = new URLSearchParams(entry.hash.slice(1)).get('token');
    return token && /^[A-Za-z0-9_-]{43}$/.test(token) ? token : null;
  } catch { return null; }
};

const normalizedName = (value: string): string => value.normalize('NFKD')
  .replace(/[\u064B-\u065F\u0670]/g, '').replace(/[^\p{L}\p{N}]/gu, '').toLocaleLowerCase('ar');

const editDistance = (left: string, right: string): number => {
  const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
  for (let row = 1; row <= left.length; row += 1) {
    const current = [row];
    for (let column = 1; column <= right.length; column += 1) {
      current[column] = Math.min(current[column - 1] + 1, previous[column] + 1,
        previous[column - 1] + (left[row - 1] === right[column - 1] ? 0 : 1));
    }
    previous.splice(0, previous.length, ...current);
  }
  return previous[right.length];
};

const namesMatch = (expected: string, actual: string): boolean => {
  const left = normalizedName(expected);
  const right = normalizedName(actual);
  const allowed = Math.max(1, Math.floor(Math.max(left.length, right.length) / 10));
  return Boolean(left && right) && editDistance(left, right) <= Math.min(2, allowed);
};

const phonesMatch = (expected: string, actual: string): boolean => {
  const left = expected.replace(/\D/g, '');
  const right = actual.replace(/\D/g, '');
  if (left.length < 7 || right.length < 7) return false;
  const compareLength = Math.min(9, left.length, right.length);
  return left.slice(-compareLength) === right.slice(-compareLength);
};

const payloadSchema = z.object({ payload: z.string().trim().min(20).max(1000) });

representativeRouter.post('/representative-invitations/claim', async (request, response) => {
  const parsed = payloadSchema.safeParse(request.body);
  const token = parsed.success ? tokenFromPayload(parsed.data.payload) : null;
  if (!token) { response.status(400).json({ error: 'Invalid representative link' }); return; }
  const result = await pool.query(
    `SELECT invitation.invited_full_name AS "invitedFullName",
            invitation.invited_phone_last_four AS "phoneLastFour",
            candidate.display_name AS "candidateName",invitation.expires_at AS "expiresAt"
     FROM guardian_representative_invitations invitation
     JOIN users candidate ON candidate.id=invitation.candidate_user_id AND candidate.is_active
     WHERE invitation.link_token_hash=$1 AND invitation.status='pending'
       AND invitation.expires_at>NOW()`, [digest(token)],
  );
  if (!result.rows[0]) { response.status(410).json({ error: 'Representative link expired or used' }); return; }
  response.json({ ...result.rows[0], enrollmentToken: token });
});

const enrollmentSchema = z.object({
  enrollmentToken: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
  fullName: z.string().trim().min(4).max(120),
  phone: z.string().trim().min(7).max(30),
  password: z.string().min(10).max(128),
  passwordConfirmation: z.string().min(10).max(128),
}).refine((value) => value.password === value.passwordConfirmation, {
  path: ['passwordConfirmation'], message: 'Passwords do not match',
});

representativeRouter.post('/representative-invitations/enroll', async (request, response) => {
  const parsed = enrollmentSchema.safeParse(request.body);
  if (!parsed.success) { response.status(400).json({ error: 'Invalid representative profile' }); return; }
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    const result = await client.query<{
      id: string; exception_id: string; candidate_user_id: string; invited_full_name: string;
      invited_phone_encrypted: string;
    }>(`SELECT id,exception_id,candidate_user_id,invited_full_name,invited_phone_encrypted
        FROM guardian_representative_invitations
        WHERE link_token_hash=$1 AND status='pending' AND expires_at>NOW() FOR UPDATE`,
    [digest(parsed.data.enrollmentToken)]);
    const invitation = result.rows[0];
    if (!invitation) {
      await client.query('ROLLBACK'); response.status(410).json({ error: 'Representative link expired or used' }); return;
    }
    if (!namesMatch(invitation.invited_full_name, parsed.data.fullName)
      || !phonesMatch(decryptSensitive(invitation.invited_phone_encrypted), parsed.data.phone)) {
      await client.query('ROLLBACK'); response.status(409).json({ error: 'Representative details do not match invitation' }); return;
    }
    const userId = randomUUID();
    const username = await generateAvailableUsername(client, parsed.data.fullName);
    const email = `${username}@representative.local`;
    await client.query(
      `INSERT INTO users (id,email,password_hash,display_name,username,role,account_type)
       VALUES ($1,$2,$3,$4,$5,'user','representative')`,
      [userId, email, await bcrypt.hash(parsed.data.password, 12), parsed.data.fullName, username],
    );
    await client.query(
      `UPDATE guardian_representative_invitations
       SET representative_user_id=$1,status='accepted',responded_at=NOW(),consumed_at=NOW(),
           link_token_hash=NULL,link_token_encrypted=NULL
       WHERE id=$2`, [userId, invitation.id],
    );
    await client.query(
      `UPDATE guardian_exceptions SET representative_user_id=$1,representative_name=$2
       WHERE id=$3 AND status='pending'`, [userId, parsed.data.fullName, invitation.exception_id],
    );
    await client.query(
      "INSERT INTO audit_events (user_id,event_type,details) VALUES ($1,'representative_link_enrolled',$2::jsonb)",
      [userId, JSON.stringify({ candidateUserId: invitation.candidate_user_id, invitationId: invitation.id })],
    );
    await client.query('COMMIT');
    const user = { id: userId, email, username, displayName: parsed.data.fullName,
      role: 'user' as const, accountType: 'representative' as const };
    response.status(201).json({ token: createToken(user, 0), user });
  } catch (error) {
    await client.query('ROLLBACK'); throw error;
  } finally { client.release(); }
});

representativeRouter.get('/me/representative-invitations', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    if (request.authUser!.accountType !== 'representative') {
      response.status(403).json({ error: 'Representative account required' }); return;
    }
    const result = await pool.query(
      `SELECT i.id,i.status,i.created_at AS "createdAt",i.responded_at AS "respondedAt",
              c.display_name AS "candidateName",c.username AS "candidateUsername",
              e.status AS "reviewStatus"
       FROM guardian_representative_invitations i
       JOIN users c ON c.id=i.candidate_user_id
       JOIN guardian_exceptions e ON e.id=i.exception_id
       WHERE i.representative_user_id=$1
       ORDER BY i.created_at DESC LIMIT 50`, [request.authUser!.id],
    );
    response.json({ invitations: result.rows });
  });

representativeRouter.post('/me/representative-invitations/:id/respond', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    const id = z.uuid().safeParse(request.params.id);
    const body = z.object({ decision: z.enum(['accepted', 'rejected']) }).safeParse(request.body);
    if (!id.success || !body.success) { response.status(400).json({ error: 'Invalid invitation response' }); return; }
    const result = await pool.query<{ candidate_user_id: string }>(
      `UPDATE guardian_representative_invitations invitation SET status=$1,responded_at=NOW()
       FROM guardian_exceptions exception
       WHERE invitation.id=$2 AND invitation.representative_user_id=$3 AND invitation.status='pending'
         AND exception.id=invitation.exception_id AND exception.status IN ('pending','approved')
       RETURNING invitation.candidate_user_id`, [body.data.decision, id.data, request.authUser!.id],
    );
    if (!result.rows[0]) { response.status(409).json({ error: 'Invitation is no longer pending' }); return; }
    response.json({ status: body.data.decision });
  });
