import { randomUUID } from 'node:crypto';
import { Router } from 'express';
import { z } from 'zod';
import { requireAdmin, requireAuth, type AuthenticatedRequest } from './auth';
import { pool } from './db';

export const profileCorrectionsRouter = Router();

const fields = {
  displayName: { column: 'display_name', schema: z.string().trim().min(2).max(80) },
  gender: { column: 'gender', schema: z.enum(['man', 'woman']) },
  birthYear: { column: 'birth_year', schema: z.number().int().min(1900).max(new Date().getUTCFullYear() - 18) },
  birthPlace: { column: 'birth_place', schema: z.string().trim().min(2).max(120) },
  skinTone: { column: 'skin_tone', schema: z.enum(['fair', 'wheat', 'olive', 'brown', 'dark']) },
  eyeColor: { column: 'eye_color', schema: z.enum(['brown', 'hazel', 'green', 'blue', 'gray', 'black', 'other']) },
  heightCm: { column: 'height_cm', schema: z.number().int().min(120).max(230) },
  appearanceNote: { column: 'appearance_note', schema: z.string().trim().max(500) },
  bio: { column: 'bio', schema: z.string().trim().min(40).max(1200) },
  valuesText: { column: 'values_text', schema: z.string().trim().min(1).max(1200) },
} as const;

type CorrectionField = keyof typeof fields;
const fieldNames = Object.keys(fields) as CorrectionField[];
const requestSchema = z.object({
  field: z.enum(fieldNames as [CorrectionField, ...CorrectionField[]]),
  proposedValue: z.unknown(),
  reason: z.string().trim().min(20).max(1000),
});

profileCorrectionsRouter.get('/me/profile-corrections', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    if (request.authUser!.role !== 'user') { response.status(403).json({ error: 'User account required' }); return; }
    const result = await pool.query(
      `SELECT id, profile_version AS "profileVersion", field, proposed_value AS "proposedValue",
              reason, status, admin_note AS "adminNote", created_at AS "createdAt",
              reviewed_at AS "reviewedAt"
       FROM profile_corrections WHERE user_id=$1 ORDER BY created_at DESC LIMIT 20`,
      [request.authUser!.id],
    );
    response.json({ corrections: result.rows });
  });

profileCorrectionsRouter.post('/me/profile-corrections', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    if (request.authUser!.role !== 'user') { response.status(403).json({ error: 'User account required' }); return; }
    const parsed = requestSchema.safeParse(request.body);
    if (!parsed.success) { response.status(400).json({ error: 'Invalid correction request' }); return; }
    const { field, reason } = parsed.data;
    const value = fields[field].schema.safeParse(parsed.data.proposedValue);
    if (!value.success) { response.status(400).json({ error: 'Invalid proposed value' }); return; }
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      const profile = await client.query<{ version: number; is_complete: boolean; current_value: unknown }>(
        `SELECT version, is_complete, ${fields[field].column} AS current_value
         FROM profiles WHERE user_id=$1 FOR UPDATE`, [request.authUser!.id],
      );
      const current = profile.rows[0];
      if (!current?.is_complete) {
        await client.query('ROLLBACK');
        response.status(409).json({ error: 'Complete the profile before requesting a correction' }); return;
      }
      if (current.current_value === value.data) {
        await client.query('ROLLBACK');
        response.status(400).json({ error: 'The proposed value is unchanged' }); return;
      }
      const result = await client.query(
        `INSERT INTO profile_corrections (id, user_id, profile_version, field, proposed_value, reason)
         VALUES ($1, $2, $3, $4, $5::jsonb, $6)
         RETURNING id, field, proposed_value AS "proposedValue", status, created_at AS "createdAt"`,
        [randomUUID(), request.authUser!.id, current.version, field, JSON.stringify(value.data), reason],
      );
      await client.query(
        "INSERT INTO audit_events (user_id, event_type, details) VALUES ($1, 'profile_correction_requested', $2::jsonb)",
        [request.authUser!.id, JSON.stringify({ correctionId: result.rows[0].id, field })],
      );
      await client.query('COMMIT');
      response.status(201).json({ correction: result.rows[0] });
    } catch (error) {
      await client.query('ROLLBACK');
      if (typeof error === 'object' && error !== null && 'code' in error && error.code === '23505') {
        response.status(409).json({ error: 'A correction request is already pending' }); return;
      }
      throw error;
    } finally { client.release(); }
  });

profileCorrectionsRouter.get('/admin/profile-corrections', requireAuth, requireAdmin,
  async (_request, response) => {
    const result = await pool.query(
      `SELECT c.id, c.user_id AS "userId", u.email, p.display_name AS "displayName",
              c.profile_version AS "profileVersion", p.version AS "currentVersion",
              c.field, c.proposed_value AS "proposedValue", c.reason, c.status,
              c.created_at AS "createdAt"
       FROM profile_corrections c JOIN users u ON u.id=c.user_id
       JOIN profiles p ON p.user_id=c.user_id
       WHERE c.status='pending' ORDER BY c.created_at ASC LIMIT 100`,
    );
    response.json({ corrections: result.rows });
  });

const reviewSchema = z.object({
  decision: z.enum(['approved', 'rejected']),
  note: z.string().trim().max(1000),
}).refine((value) => value.decision === 'approved' || value.note.length >= 10);

profileCorrectionsRouter.patch('/admin/profile-corrections/:id/review', requireAuth, requireAdmin,
  async (request: AuthenticatedRequest, response) => {
    const id = z.uuid().safeParse(request.params.id);
    const parsed = reviewSchema.safeParse(request.body);
    if (!id.success || !parsed.success) { response.status(400).json({ error: 'Invalid correction review' }); return; }
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      const result = await client.query<{
        id: string; user_id: string; profile_version: number; field: CorrectionField;
        proposed_value: unknown; status: string;
      }>('SELECT * FROM profile_corrections WHERE id=$1 FOR UPDATE', [id.data]);
      const correction = result.rows[0];
      if (!correction || correction.status !== 'pending') {
        await client.query('ROLLBACK');
        response.status(409).json({ error: 'Correction is no longer pending' }); return;
      }
      const profile = await client.query<{ version: number }>(
        'SELECT version FROM profiles WHERE user_id=$1 FOR UPDATE', [correction.user_id],
      );
      if (parsed.data.decision === 'approved') {
        if (profile.rows[0]?.version !== correction.profile_version) {
          await client.query('ROLLBACK');
          response.status(409).json({ error: 'Profile changed since the correction was requested' }); return;
        }
        const field = fields[correction.field];
        if (!field || !field.schema.safeParse(correction.proposed_value).success) {
          await client.query('ROLLBACK');
          response.status(409).json({ error: 'Correction value is no longer valid' }); return;
        }
        await client.query(
          `UPDATE profiles SET ${field.column}=$1, version=version+1,
             is_published=TRUE,publication_requested=TRUE,approval_status='approved',reviewed_by=NULL,
             reviewed_at=NULL, review_note='', updated_at=NOW()
           WHERE user_id=$2`, [correction.proposed_value, correction.user_id],
        );
        if (correction.field === 'displayName') {
          await client.query('UPDATE users SET display_name=$1 WHERE id=$2',
            [correction.proposed_value, correction.user_id]);
        }
      }
      const updated = await client.query(
        `UPDATE profile_corrections SET status=$1, admin_note=$2, reviewed_by=$3, reviewed_at=NOW()
         WHERE id=$4 RETURNING id, status`,
        [parsed.data.decision, parsed.data.note, request.authUser!.id, correction.id],
      );
      await client.query(
        "INSERT INTO audit_events (user_id, event_type, details) VALUES ($1, 'profile_correction_reviewed', $2::jsonb)",
        [request.authUser!.id, JSON.stringify({ correctionId: correction.id, userId: correction.user_id,
          field: correction.field, decision: parsed.data.decision })],
      );
      await client.query('COMMIT');
      response.json({ correction: updated.rows[0] });
    } catch (error) { await client.query('ROLLBACK'); throw error; }
    finally { client.release(); }
  });
