import { 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 { matchReasons, profileCompatibilityScore, profilesMatch, type MatchableProfile } from './profileMatching';
import { recordRequestEvent } from './relationshipEvents';
import { notifyUsers } from './notifications';

export const sharedJourneyRouter = Router();

export const profileFields = `p.user_id AS "userId", p.display_name AS "displayName",
  p.city, p.country, p.bio, p.birth_year AS "birthYear", p.birth_place AS "birthPlace", p.gender,
  p.religion,
  p.marital_status AS "maritalStatus", p.children_count AS "childrenCount",
  p.education, p.occupation, p.values_text AS "valuesText",
  p.music_preferences AS "musicPreferences",
  p.skin_tone AS "skinTone", p.eye_color AS "eyeColor", p.hair_color AS "hairColor",
  p.height_cm AS "heightCm", p.weight_kg AS "weightKg", p.body_build AS "bodyBuild",
  p.appearance_note AS "appearanceNote",
  p.partner_description AS "partnerDescription", p.preferred_gender AS "preferredGender",
  p.preferred_min_age AS "preferredMinAge", p.preferred_max_age AS "preferredMaxAge",
  p.preferred_country AS "preferredCountry",
  p.preferred_marital_status AS "preferredMaritalStatus",
  p.preferred_education AS "preferredEducation",
  p.preferred_religion AS "preferredReligion",
  p.preferred_eye_colors AS "preferredEyeColors",
  p.preferred_hair_colors AS "preferredHairColors",
  p.accepts_partner_children AS "acceptsPartnerChildren",
  p.preferred_max_children AS "preferredMaxChildren",
  p.identity_status AS "identityStatus",
  EXISTS (SELECT 1 FROM profile_photos photo WHERE photo.user_id=p.user_id) AS "hasPhoto",
  p.is_complete AS "isComplete", p.publication_requested AS "publicationRequested",
  p.approval_status AS "approvalStatus",
  (p.is_published AND p.is_complete AND p.approval_status = 'approved') AS "isPublished",
  p.version, p.updated_at AS "updatedAt"`;

type ProfileRow = MatchableProfile;

const requireUser = (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;
};

sharedJourneyRouter.get('/me/profile', requireAuth, async (request: AuthenticatedRequest, response) => {
  if (!requireUser(request, response)) return;
  await pool.query(
    `INSERT INTO profiles (user_id, display_name)
     VALUES ($1, $2) ON CONFLICT (user_id) DO NOTHING`,
    [request.authUser!.id, request.authUser!.displayName],
  );
  const result = await pool.query(
    `SELECT ${profileFields}, p.review_note AS "reviewNote" FROM profiles p WHERE p.user_id = $1`,
    [request.authUser!.id],
  );
  response.json({ profile: result.rows[0] });
});

const genderSchema = z.enum(['man', 'woman']);
const maritalStatusSchema = z.enum(['single', 'married', 'divorced', 'widowed']);
const educationSchema = z.enum(['secondary', 'diploma', 'bachelor', 'master', 'doctorate', 'other']);
const skinToneSchema = z.enum(['fair', 'wheat', 'olive', 'brown', 'dark']);
const eyeColorSchema = z.enum(['brown', 'hazel', 'green', 'blue', 'gray', 'black', 'other']);
const hairColorSchema = z.enum(['black', 'brown', 'blonde', 'red', 'gray', 'bald', 'other']);
const bodyBuildSchema = z.enum(['slim', 'average', 'athletic', 'full']);
const religionSchema = z.enum(['muslim', 'non_muslim']);
const musicGenreSchema = z.enum([
  'none', 'romantic', 'sad', 'rap', 'classical', 'pop',
  'traditional', 'religious', 'electronic', 'dancing', 'mahraganat', 'other',
]);
const preferenceArray = <T extends z.ZodTypeAny>(schema: T) => z.array(z.union([schema, z.literal('any')]))
  .min(1).max(8).refine((items) => !items.includes('any') || items.length === 1,
    'Does not matter must be selected alone');
export const profileSchema = z.object({
  displayName: z.string().trim().min(2).max(80),
  gender: genderSchema,
  birthYear: z.number().int().min(1900).max(new Date().getUTCFullYear() - 18),
  birthPlace: z.string().trim().min(2).max(120),
  country: z.string().trim().min(2).max(100),
  city: z.string().trim().min(2).max(120),
  religion: religionSchema,
  maritalStatus: maritalStatusSchema,
  childrenCount: z.number().int().min(0).max(5),
  education: educationSchema,
  occupation: z.string().trim().min(2).max(120),
  skinTone: skinToneSchema,
  eyeColor: eyeColorSchema,
  hairColor: hairColorSchema,
  heightCm: z.number().int().min(120).max(230),
  weightKg: z.number().int().min(30).max(300),
  bodyBuild: bodyBuildSchema,
  appearanceNote: z.string().trim().max(500),
  bio: z.string().trim().min(40).max(1200),
  valuesText: z.string().trim().min(1).max(1200),
  musicPreferences: z.array(musicGenreSchema).min(1).max(5)
    .refine((items) => !items.includes('none') || items.length === 1,
      'No music must be selected alone'),
  partnerDescription: z.string().trim().min(1).max(1200),
  preferredGender: genderSchema,
  preferredMinAge: z.number().int().min(18).max(100),
  preferredMaxAge: z.number().int().min(18).max(100),
  preferredCountry: z.string().trim().min(2).max(100),
  preferredMaritalStatus: z.union([maritalStatusSchema, z.literal('any')]),
  preferredEducation: z.union([educationSchema, z.literal('any')]),
  preferredReligion: religionSchema,
  preferredEyeColors: preferenceArray(eyeColorSchema),
  preferredHairColors: preferenceArray(hairColorSchema),
  acceptsPartnerChildren: z.boolean(),
  preferredMaxChildren: z.number().int().min(0).max(5),
  publicationRequested: z.boolean(),
  expectedVersion: z.number().int().min(1),
}).refine((value) => value.preferredMinAge <= value.preferredMaxAge, {
  message: 'Minimum preferred age must not exceed maximum preferred age',
}).refine((value) => !(value.gender === 'woman' && value.maritalStatus === 'married'), {
  message: 'A married woman profile is not allowed',
  path: ['maritalStatus'],
}).refine((value) => !(value.gender === 'man' && value.preferredMaritalStatus === 'married'), {
  message: 'A man cannot select a married woman as a partner preference',
  path: ['preferredMaritalStatus'],
}).refine((value) => !(value.gender === 'man' && value.preferredHairColors.includes('bald')), {
  message: 'A man cannot select bald as a female partner hair preference',
  path: ['preferredHairColors'],
}).refine((value) => value.acceptsPartnerChildren
  ? value.preferredMaxChildren >= 1 : value.preferredMaxChildren === 0, {
  message: 'Maximum accepted children must match the children preference',
  path: ['preferredMaxChildren'],
});

sharedJourneyRouter.patch('/me/profile', requireAuth, async (request: AuthenticatedRequest, response) => {
  if (!requireUser(request, response)) return;
  const parsed = profileSchema.safeParse(request.body);
  if (!parsed.success) {
    response.status(400).json({ error: 'Invalid profile data' });
    return;
  }
  const value = {
    ...parsed.data,
    city: parsed.data.country,
    preferredGender: parsed.data.gender === 'man' ? 'woman' as const : 'man' as const,
    preferredReligion: parsed.data.religion,
  };
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    const currentResult = await client.query<{
      displayName: string; gender: string | null; birthYear: number | null; birthPlace: string;
      religion: string | null; skinTone: string | null; eyeColor: string | null; heightCm: number | null;
      appearanceNote: string; bio: string; valuesText: string;
      isComplete: boolean; version: number;
    }>(
      `SELECT display_name AS "displayName", gender, birth_year AS "birthYear",
         birth_place AS "birthPlace", religion, skin_tone AS "skinTone", eye_color AS "eyeColor",
         height_cm AS "heightCm", appearance_note AS "appearanceNote",
         bio, values_text AS "valuesText", is_complete AS "isComplete", version
       FROM profiles WHERE user_id=$1 FOR UPDATE`,
      [request.authUser!.id],
    );
    const current = currentResult.rows[0];
    if (!current || current.version !== value.expectedVersion) {
      await client.query('ROLLBACK');
      response.status(409).json({ error: 'Profile changed on another device. Reload before saving.' });
      return;
    }
    if (current.isComplete && (
      current.displayName !== value.displayName || current.gender !== value.gender
      || current.birthYear !== value.birthYear || current.birthPlace !== value.birthPlace
      || (current.religion !== null && current.religion !== value.religion)
      || current.skinTone !== value.skinTone || current.eyeColor !== value.eyeColor
      || current.heightCm !== value.heightCm
      || current.appearanceNote !== value.appearanceNote || current.bio !== value.bio
      || current.valuesText !== value.valuesText
    )) {
      await client.query('ROLLBACK');
      response.status(409).json({ error: 'Fixed profile details are locked after first completion.' });
      return;
    }
    const updated = await client.query(
      `UPDATE profiles p
       SET display_name=$1, gender=$2, birth_year=$3, birth_place=$4, country=$5, city=$6,
           marital_status=$7, children_count=$8, has_children=($8::integer > 0),
           education=$9, occupation=$10, bio=$11, values_text=$12,
           partner_description=$13, skin_tone=$14, eye_color=$15, hair_color=$16,
           height_cm=$17, weight_kg=$18, body_build=$19, appearance_note=$20,
           preferred_gender=$21, preferred_min_age=$22, preferred_max_age=$23,
           preferred_country=$24, preferred_marital_status=$25, preferred_education=$26,
           accepts_partner_children=$27,preferred_max_children=$28,
           is_complete=TRUE,publication_requested=TRUE,
           religion=$29,preferred_religion=$30,preferred_eye_colors=$31,preferred_hair_colors=$32,
           music_preferences=$33,
           is_published=TRUE,approval_status='approved',reviewed_by=NULL,
           reviewed_at=NULL, review_note='',
           version=version+1, updated_at=NOW()
       WHERE p.user_id=$34 AND p.version=$35
       RETURNING ${profileFields}, p.review_note AS "reviewNote"`,
      [value.displayName, value.gender, value.birthYear, value.birthPlace, value.country, value.city,
        value.maritalStatus, value.childrenCount, value.education, value.occupation,
        value.bio, value.valuesText, value.partnerDescription, value.skinTone, value.eyeColor,
        value.hairColor, value.heightCm, value.weightKg, value.bodyBuild, value.appearanceNote,
        value.preferredGender, value.preferredMinAge, value.preferredMaxAge, value.preferredCountry,
        value.preferredMaritalStatus, value.preferredEducation, value.acceptsPartnerChildren,
        value.preferredMaxChildren, value.religion, value.preferredReligion,
        value.preferredEyeColors, value.preferredHairColors, value.musicPreferences,
        request.authUser!.id, value.expectedVersion],
    );
    if (!updated.rowCount) {
      await client.query('ROLLBACK');
      response.status(409).json({ error: 'Profile changed on another device. Reload before saving.' });
      return;
    }
    await client.query('UPDATE users SET display_name=$1 WHERE id=$2', [
      value.displayName, request.authUser!.id,
    ]);
    await client.query(
      "INSERT INTO audit_events (user_id, event_type, details) VALUES ($1, 'profile_updated', $2::jsonb)",
      [request.authUser!.id, JSON.stringify({ version: updated.rows[0].version })],
    );
    await client.query('COMMIT');
    response.json({ profile: updated.rows[0] });
  } catch (error) {
    await client.query('ROLLBACK');
    throw error;
  } finally {
    client.release();
  }
});

sharedJourneyRouter.get('/admin/profiles', requireAuth, requireAdmin, async (_request, response) => {
  const result = await pool.query(
    `SELECT ${profileFields}, p.review_note AS "reviewNote", u.email
     FROM profiles p JOIN users u ON u.id=p.user_id
     WHERE p.is_complete AND p.publication_requested AND u.role='user'
     ORDER BY CASE WHEN p.approval_status='pending' THEN 0 ELSE 1 END,
              p.updated_at DESC LIMIT 200`,
  );
  response.json({ profiles: result.rows });
});

const reviewProfileSchema = z.object({
  decision: z.enum(['approved', 'rejected']),
  note: z.string().trim().max(1000),
}).refine((value) => value.decision !== 'rejected' || value.note.length >= 3, {
  message: 'Rejection requires a reason',
});

sharedJourneyRouter.patch('/admin/profiles/:userId/review', requireAuth, requireAdmin,
  async (request: AuthenticatedRequest, response) => {
    const parsed = reviewProfileSchema.safeParse(request.body);
    if (!parsed.success || !z.uuid().safeParse(request.params.userId).success) {
      response.status(400).json({ error: 'Invalid profile review' }); return;
    }
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      const result = await client.query(
        `UPDATE profiles p SET approval_status=$1, review_note=$2,
           reviewed_by=$3, reviewed_at=NOW(),
           is_published=($1='approved'), version=version+1, updated_at=NOW()
         WHERE p.user_id=$4 AND p.is_complete AND p.publication_requested
           AND (p.approval_status='pending' OR ($1='rejected' AND p.approval_status='approved'))
         RETURNING ${profileFields}`,
        [parsed.data.decision, parsed.data.note, request.authUser!.id, request.params.userId],
      );
      if (!result.rowCount) {
        await client.query('ROLLBACK');
        response.status(409).json({ error: 'Profile is not reviewable in its current state' }); return;
      }
      await client.query(
        `INSERT INTO audit_events (user_id, event_type, details)
         VALUES ($1, 'profile_reviewed', $2::jsonb)`,
        [request.authUser!.id, JSON.stringify({ targetUserId: request.params.userId,
          decision: parsed.data.decision, note: parsed.data.note })],
      );
      await client.query('COMMIT');
      response.json({ profile: result.rows[0] });
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally { client.release(); }
  });

sharedJourneyRouter.get('/me/blocks', requireAuth, async (request: AuthenticatedRequest, response) => {
  if (!requireUser(request, response)) return;
  const result = await pool.query(
    `SELECT b.blocked_user_id AS "userId", p.display_name AS "displayName",
       b.created_at AS "createdAt"
     FROM user_blocks b JOIN profiles p ON p.user_id=b.blocked_user_id
     WHERE b.blocker_user_id=$1 ORDER BY b.created_at DESC`,
    [request.authUser!.id],
  );
  response.json({ blocks: result.rows });
});

sharedJourneyRouter.post('/me/blocks', requireAuth, async (request: AuthenticatedRequest, response) => {
  if (!requireUser(request, response)) return;
  const parsed = z.object({ targetUserId: z.uuid() }).safeParse(request.body);
  if (!parsed.success || parsed.data.targetUserId === request.authUser!.id) {
    response.status(400).json({ error: 'Invalid blocked account' }); return;
  }
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    const target = await client.query('SELECT id FROM users WHERE id=$1 AND role=$2',
      [parsed.data.targetUserId, 'user']);
    if (!target.rowCount) {
      await client.query('ROLLBACK'); response.status(404).json({ error: 'Account not found' }); return;
    }
    await client.query(
      `INSERT INTO user_blocks (blocker_user_id, blocked_user_id) VALUES ($1, $2)
       ON CONFLICT DO NOTHING`,
      [request.authUser!.id, parsed.data.targetUserId],
    );
    await client.query(
      `UPDATE engagement_requests SET status='declined', updated_at=NOW()
       WHERE status='pending' AND
         ((sender_user_id=$1 AND recipient_user_id=$2) OR
          (sender_user_id=$2 AND recipient_user_id=$1))`,
      [request.authUser!.id, parsed.data.targetUserId],
    );
    await client.query(
      `INSERT INTO audit_events (user_id, event_type, details)
       VALUES ($1, 'user_blocked', $2::jsonb)`,
      [request.authUser!.id, JSON.stringify({ targetUserId: parsed.data.targetUserId })],
    );
    await client.query('COMMIT');
    response.json({ blocked: true });
  } catch (error) {
    await client.query('ROLLBACK'); throw error;
  } finally { client.release(); }
});

sharedJourneyRouter.delete('/me/blocks/:targetUserId', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    if (!requireUser(request, response)) return;
    const parsed = z.uuid().safeParse(request.params.targetUserId);
    if (!parsed.success) { response.status(400).json({ error: 'Invalid blocked account' }); return; }
    const result = await pool.query(
      'DELETE FROM user_blocks WHERE blocker_user_id=$1 AND blocked_user_id=$2 RETURNING blocked_user_id',
      [request.authUser!.id, parsed.data],
    );
    if (!result.rowCount) { response.status(404).json({ error: 'Blocked account not found' }); return; }
    await pool.query(
      `INSERT INTO audit_events (user_id, event_type, details)
       VALUES ($1, 'user_unblocked', $2::jsonb)`,
      [request.authUser!.id, JSON.stringify({ targetUserId: parsed.data })],
    );
    response.json({ blocked: false });
  });

sharedJourneyRouter.get('/discover', requireAuth, async (request: AuthenticatedRequest, response) => {
  if (!requireUser(request, response)) return;
  const mine = await pool.query<ProfileRow>(
    `SELECT ${profileFields} FROM profiles p WHERE p.user_id=$1`, [request.authUser!.id],
  );
  if (!mine.rows[0]?.isComplete || !mine.rows[0]?.isPublished) {
    response.json({ profiles: [], profileRequired: true }); return;
  }
  const result = await pool.query<ProfileRow>(
    `SELECT ${profileFields}
     FROM profiles p JOIN users u ON u.id = p.user_id
     WHERE p.user_id <> $1 AND p.is_published AND p.is_complete
       AND p.approval_status='approved' AND u.is_active AND u.role = 'user'
       AND NOT EXISTS (
         SELECT 1 FROM user_blocks b
         WHERE (b.blocker_user_id=$1 AND b.blocked_user_id=p.user_id)
            OR (b.blocker_user_id=p.user_id AND b.blocked_user_id=$1))
       AND NOT EXISTS (
         SELECT 1 FROM journeys j
         WHERE j.status='active' AND (
           (j.first_user_id=$1 AND j.second_user_id=p.user_id)
           OR (j.first_user_id=p.user_id AND j.second_user_id=$1)))
       AND NOT EXISTS (
         SELECT 1 FROM engagement_requests r
         WHERE r.sender_user_id=$1 AND r.recipient_user_id=p.user_id
           AND r.status='declined' AND r.updated_at > NOW()-INTERVAL '7 days')
     ORDER BY p.updated_at DESC, p.user_id LIMIT 200`,
    [request.authUser!.id],
  );
  response.json({ profiles: result.rows.filter((candidate) => profilesMatch(mine.rows[0], candidate))
    .sort((first, second) => profileCompatibilityScore(mine.rows[0], second)
      - profileCompatibilityScore(mine.rows[0], first))
    .slice(0, 50).map((candidate) => ({ ...candidate, matchReasons: matchReasons(mine.rows[0], candidate) })) });
});

const requestFields = `r.id, r.sender_user_id AS "senderUserId",
  r.recipient_user_id AS "recipientUserId", r.status,
  r.created_at AS "createdAt", r.updated_at AS "updatedAt"`;

sharedJourneyRouter.get('/requests', requireAuth, async (request: AuthenticatedRequest, response) => {
  if (!requireUser(request, response)) return;
  const result = await pool.query(
    `SELECT ${requestFields},
       CASE WHEN r.sender_user_id = $1 THEN 'outgoing' ELSE 'incoming' END AS direction,
       p.display_name AS "counterpartName", p.country AS "counterpartCity"
     FROM engagement_requests r
     JOIN profiles p ON p.user_id = CASE
       WHEN r.sender_user_id = $1 THEN r.recipient_user_id ELSE r.sender_user_id END
     WHERE r.sender_user_id = $1 OR r.recipient_user_id = $1
     ORDER BY r.created_at DESC LIMIT 100`,
    [request.authUser!.id],
  );
  response.json({ requests: result.rows });
});

const newRequestSchema = z.object({
  recipientUserId: z.uuid(),
});

sharedJourneyRouter.post('/requests', requireAuth, async (request: AuthenticatedRequest, response) => {
  if (!requireUser(request, response)) return;
  const parsed = newRequestSchema.safeParse(request.body);
  if (!parsed.success || parsed.data.recipientUserId === request.authUser!.id) {
    response.status(400).json({ error: 'Invalid request' });
    return;
  }
  const recipient = await pool.query<ProfileRow>(
    `SELECT ${profileFields} FROM profiles p JOIN users u ON u.id=p.user_id
     WHERE p.user_id=$1 AND p.is_published AND p.is_complete
       AND p.approval_status='approved' AND u.is_active AND u.role='user'`,
    [parsed.data.recipientUserId],
  );
  const sender = await pool.query<ProfileRow>(
    `SELECT ${profileFields} FROM profiles p WHERE p.user_id=$1 AND p.is_published
       AND p.is_complete AND p.approval_status='approved'`,
    [request.authUser!.id],
  );
  if (!recipient.rows[0] || !sender.rows[0] || !profilesMatch(sender.rows[0], recipient.rows[0])) {
    response.status(409).json({ error: 'Profiles must be complete, published, and match current preferences' });
    return;
  }
  const blocked = await pool.query(
    `SELECT 1 FROM user_blocks b WHERE
       (b.blocker_user_id=$1 AND b.blocked_user_id=$2) OR
       (b.blocker_user_id=$2 AND b.blocked_user_id=$1) LIMIT 1`,
    [request.authUser!.id, parsed.data.recipientUserId],
  );
  if (blocked.rowCount) { response.status(409).json({ error: 'Contact between these accounts is blocked' }); return; }
  const unavailable = await pool.query(
    `SELECT 1 FROM journeys j WHERE j.status='active' AND (
       (j.first_user_id=$1 AND j.second_user_id=$2) OR
       (j.first_user_id=$2 AND j.second_user_id=$1))
     UNION ALL
     SELECT 1 FROM engagement_requests r
     WHERE r.sender_user_id=$1 AND r.recipient_user_id=$2
       AND r.status='declined' AND r.updated_at > NOW()-INTERVAL '7 days'
     LIMIT 1`,
    [request.authUser!.id, parsed.data.recipientUserId],
  );
  if (unavailable.rowCount) {
    response.status(409).json({ error: 'This profile is not currently available for a new request' });
    return;
  }
  const id = randomUUID();
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    const result = await client.query(
      `INSERT INTO engagement_requests AS r
         (id, sender_user_id, recipient_user_id)
       VALUES ($1, $2, $3) RETURNING ${requestFields}`,
      [id, request.authUser!.id, parsed.data.recipientUserId],
    );
    await client.query(
      "INSERT INTO audit_events (user_id, event_type, details) VALUES ($1, 'engagement_request_sent', $2::jsonb)",
      [request.authUser!.id, JSON.stringify({ requestId: id })],
    );
    await client.query('COMMIT');
    await recordRequestEvent(id, request.authUser!.id, 'request_sent');
    await notifyUsers([parsed.data.recipientUserId], {
      type: 'engagement_request', title: 'طلب تعارف جديد',
      body: `${request.authUser!.displayName} أرسل إليك طلب تعارف.`,
      requestId: id, dedupeKey: `engagement-request:${id}`,
    });
    response.status(201).json({ request: 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: 'An active request already exists for this pair' });
      return;
    }
    throw error;
  } finally {
    client.release();
  }
});
