import type { Response } from 'express';
import { z } from 'zod';
import type { AuthenticatedRequest } from './auth';
import { pool } from './db';
import { liveKitReady } from './liveKitMedia';
import { meetingPaymentsReady } from './paymentService';

export type CallRole = 'groom' | 'groomGuardian' | 'bride' | 'brideGuardian';
export interface CallSlot { role: CallRole; userId: string | null; displayName: string | null;
  online: boolean; sessionId: string | null; joinedOnce: boolean; mediaConnectedOnce: boolean;
  isRepresentative: boolean }
export interface CallContext {
  journeyId: string; journeyStatus: string; meetingId: string | null;
  meetingStatus: string | null; startsAt: Date | null; videoRequired: boolean;
  callEndedAt: Date | null;
  firstId: string; secondId: string; firstGender: string | null; secondGender: string | null;
  requestSenderId: string;
}

export const readContext = async (journeyId: string): Promise<CallContext | null> => {
  const result = await pool.query<CallContext>(
    `SELECT j.id AS "journeyId", j.status AS "journeyStatus",
            m.id AS "meetingId", m.status AS "meetingStatus", m.starts_at AS "startsAt",
            COALESCE(m.video_required, FALSE) AS "videoRequired",
            m.call_ended_at AS "callEndedAt",
            j.first_user_id AS "firstId", j.second_user_id AS "secondId",
            fp.gender AS "firstGender", sp.gender AS "secondGender",
            relationship.sender_user_id AS "requestSenderId"
     FROM journeys j JOIN profiles fp ON fp.user_id=j.first_user_id
     JOIN profiles sp ON sp.user_id=j.second_user_id
     JOIN engagement_requests relationship ON relationship.id=j.request_id
     LEFT JOIN LATERAL (
       SELECT * FROM meeting_proposals WHERE journey_id=j.id
       ORDER BY created_at DESC LIMIT 1
     ) m ON TRUE WHERE j.id=$1`, [journeyId],
  );
  return result.rows[0] ?? null;
};

const guardianRows = async (journeyId: string) => {
  const result = await pool.query<{
    participantId: string; guardianId: string | null; guardianName: string | null;
    selectionMode: 'guardian' | 'exception';
  }>(
    `SELECT g.participant_user_id AS "participantId", g.guardian_user_id AS "guardianId",
            u.display_name AS "guardianName", g.selection_mode AS "selectionMode"
     FROM journey_call_guardians g
     LEFT JOIN users u ON u.id=g.guardian_user_id AND u.is_active
       AND (g.selection_mode='guardian'
         OR (g.selection_mode='exception' AND u.account_type='representative'))
     LEFT JOIN guardian_invitations v ON v.id=g.verification_id
       AND v.candidate_user_id=g.participant_user_id AND v.accepted_by=g.guardian_user_id
       AND v.status='verified'
     LEFT JOIN guardian_exceptions e ON e.id=g.exception_id
       AND e.candidate_user_id=g.participant_user_id AND e.status='approved'
       AND e.contact_confirmed_at IS NOT NULL
       AND e.representative_user_id=g.guardian_user_id
     LEFT JOIN guardian_representative_invitations ri ON ri.exception_id=e.id
       AND ri.representative_user_id=g.guardian_user_id AND ri.status='accepted'
     WHERE g.journey_id=$1 AND (
       (g.selection_mode='guardian' AND u.id IS NOT NULL AND v.id IS NOT NULL AND EXISTS (
         SELECT 1 FROM journey_memberships jm WHERE jm.journey_id=g.journey_id
           AND jm.user_id=g.guardian_user_id AND jm.is_active
       )) OR (g.selection_mode='exception' AND e.id IS NOT NULL AND ri.id IS NOT NULL AND u.id IS NOT NULL
         AND EXISTS (
           SELECT 1 FROM journey_memberships jm WHERE jm.journey_id=g.journey_id
             AND jm.user_id=g.guardian_user_id AND jm.is_active
         ))
     )`, [journeyId],
  );
  return result.rows;
};

export const authorizedContext = async (
  request: AuthenticatedRequest,
  response: Response,
): Promise<CallContext | null> => {
  const id = z.uuid().safeParse(request.params.id);
  if (!id.success) { response.status(400).json({ error: 'Invalid journey ID' }); return null; }
  const context = await readContext(id.data);
  if (!context) { response.status(404).json({ error: 'Journey not found' }); return null; }
  const isParticipant = [context.firstId, context.secondId].includes(request.authUser!.id);
  if (!isParticipant) {
    const membership = await pool.query(
      'SELECT 1 FROM journey_memberships WHERE journey_id=$1 AND user_id=$2 AND is_active',
      [id.data, request.authUser!.id],
    );
    if (!membership.rowCount) { response.status(404).json({ error: 'Journey not found' }); return null; }
  }
  return context;
};

export const readSlots = async (context: CallContext): Promise<CallSlot[]> => {
  const guardians = await guardianRows(context.journeyId);
  const groomId = context.firstGender === 'man' ? context.firstId : context.secondId;
  const brideId = context.firstGender === 'woman' ? context.firstId : context.secondId;
  const people = await pool.query<{ id: string; display_name: string }>(
    'SELECT id, display_name FROM users WHERE id = ANY($1::uuid[])', [[groomId, brideId]],
  );
  const names = new Map(people.rows.map((person) => [person.id, person.display_name]));
  const guardianFor = (id: string) => guardians.find((item) => item.participantId === id);
  const groomGuardian = guardianFor(groomId);
  const brideGuardian = guardianFor(brideId);
  const seats: CallSlot[] = [
    { role: 'groom', userId: groomId, displayName: names.get(groomId) ?? null, online: false, sessionId: null, joinedOnce: false, mediaConnectedOnce: false, isRepresentative: false },
    { role: 'groomGuardian', userId: groomGuardian?.guardianId ?? null, displayName: groomGuardian?.guardianName ?? null, online: false, sessionId: null, joinedOnce: false, mediaConnectedOnce: false, isRepresentative: groomGuardian?.selectionMode === 'exception' },
    { role: 'bride', userId: brideId, displayName: names.get(brideId) ?? null, online: false, sessionId: null, joinedOnce: false, mediaConnectedOnce: false, isRepresentative: false },
    { role: 'brideGuardian', userId: brideGuardian?.guardianId ?? null, displayName: brideGuardian?.guardianName ?? null, online: false, sessionId: null, joinedOnce: false, mediaConnectedOnce: false, isRepresentative: brideGuardian?.selectionMode === 'exception' },
  ];
  if (context.meetingId) {
    await pool.query(
      `UPDATE meeting_call_sessions SET left_at=NOW()
       WHERE meeting_id=$1 AND left_at IS NULL AND last_seen_at < NOW()-INTERVAL '20 seconds'`,
      [context.meetingId],
    );
    const sessions = await pool.query<{ user_id: string; session_id: string; online: boolean }>(
      `SELECT user_id, session_id,
              (left_at IS NULL AND last_seen_at >= NOW()-INTERVAL '20 seconds') AS online
       FROM meeting_call_sessions WHERE meeting_id=$1 ORDER BY joined_at DESC`,
      [context.meetingId],
    );
    const linked = await pool.query<{ user_id: string }>(
      `SELECT DISTINCT s.user_id FROM meeting_call_sessions s
       JOIN meeting_call_links l ON l.meeting_id=s.meeting_id
         AND s.session_id IN (l.lower_session_id, l.upper_session_id)
         AND l.lower_confirmed AND l.upper_confirmed
       WHERE s.meeting_id=$1`, [context.meetingId],
    );
    const connectedUsers = new Set(linked.rows.map((item) => item.user_id));
    for (const seat of seats) {
      if (!seat.userId) continue;
      const history = sessions.rows.filter((item) => item.user_id === seat.userId);
      seat.joinedOnce = history.length > 0;
      seat.mediaConnectedOnce = connectedUsers.has(seat.userId);
      const active = history.find((item) => item.online);
      seat.online = Boolean(active);
      seat.sessionId = active?.session_id ?? null;
    }
  }
  return seats;
};

const callWindow = (context: CallContext): boolean => Boolean(
  context.meetingStatus === 'confirmed' && context.startsAt
  && Date.now() <= context.startsAt.getTime() + 6 * 60 * 60_000,
);

export const hasFourWayMediaConnection = async (journeyId: string, meetingId: string): Promise<boolean> => {
  const context = await readContext(journeyId);
  if (!context || context.meetingId !== meetingId) return false;
  const slots = await readSlots(context);
  const ids = slots.map((slot) => slot.userId).filter((id): id is string => Boolean(id));
  if (ids.length !== 4 || new Set(ids).size !== 4) return false;
  const links = await pool.query<{ first_user_id: string; second_user_id: string }>(
    `SELECT a.user_id AS first_user_id, b.user_id AS second_user_id
     FROM meeting_call_links l
     JOIN meeting_call_sessions a ON a.session_id=l.lower_session_id
     JOIN meeting_call_sessions b ON b.session_id=l.upper_session_id
     WHERE l.meeting_id=$1 AND l.lower_confirmed AND l.upper_confirmed`, [meetingId],
  );
  const visited = new Set<string>([ids[0]!]);
  let changed = true;
  while (changed) {
    changed = false;
    for (const link of links.rows) {
      if (visited.has(link.first_user_id) && !visited.has(link.second_user_id)) {
        visited.add(link.second_user_id); changed = true;
      }
      if (visited.has(link.second_user_id) && !visited.has(link.first_user_id)) {
        visited.add(link.first_user_id); changed = true;
      }
    }
  }
  return ids.every((id) => visited.has(id));
};

export const publicCallState = async (context: CallContext, actorId: string) => {
  const slots = await readSlots(context);
  const guardianSlots = slots.filter((slot) => slot.role.endsWith('Guardian'));
  const guardianIds = guardianSlots.map((slot) => slot.userId)
    .filter((id): id is string => Boolean(id));
  const scheduleConfirmations = context.meetingId
    ? await pool.query<{ user_id: string }>(
      'SELECT user_id FROM meeting_schedule_confirmations WHERE meeting_id=$1', [context.meetingId],
    ) : { rows: [] as Array<{ user_id: string }> };
  const confirmedGuardians = new Set(scheduleConfirmations.rows.map((item) => item.user_id));
  const seatIds = slots.map((slot) => slot.userId).filter((id): id is string => Boolean(id));
  const ready = guardianSlots.every((slot) => Boolean(slot.userId))
    && seatIds.length === 4 && new Set(seatIds).size === 4;
  const guardiansPresent = guardianSlots.every((slot) => slot.online);
  const pairReady = (context.firstGender === 'man' && context.secondGender === 'woman')
    || (context.firstGender === 'woman' && context.secondGender === 'man');
  const allowed = context.journeyStatus === 'active' && !context.callEndedAt && pairReady
    && ready && callWindow(context);
  const actorSlot = slots.find((slot) => slot.userId === actorId);
  const actorIsGuardian = actorSlot?.role.endsWith('Guardian') ?? false;
  const mainPartiesConfirmed = context.meetingStatus === 'confirmed' || context.meetingStatus === 'completed';
  const allPartiesConfirmed = mainPartiesConfirmed && guardianIds.length === 2
    && guardianIds.every((id) => confirmedGuardians.has(id));
  const paymentsReady = await meetingPaymentsReady(context.meetingId);
  return {
    meetingId: context.meetingId, meetingStatus: context.meetingStatus,
    startsAt: context.startsAt, videoRequired: context.videoRequired,
    slots, canJoin: Boolean(allowed && allPartiesConfirmed && paymentsReady && actorSlot
      && (actorIsGuardian || guardiansPresent)),
    guardiansReady: ready, guardiansPresent, pairReady, callWindowOpen: callWindow(context),
    confirmationCount: mainPartiesConfirmed
      ? 2 + guardianIds.filter((id) => confirmedGuardians.has(id)).length
      : context.meetingStatus === 'proposed' ? 1 : 0,
    confirmationRequired: 4, allPartiesConfirmed, paymentsReady,
    iConfirmedSchedule: confirmedGuardians.has(actorId),
    canConfirmSchedule: Boolean(actorIsGuardian && mainPartiesConfirmed && !confirmedGuardians.has(actorId)),
    mediaProvider: { name: 'livekit' as const, configured: liveKitReady() },
    endedAt: context.callEndedAt,
  };
};
