/**
 * Relationship Manager Module
 * Handles romantic relationships, dating activities, affinity updates, and relationship data
 * Ported from Python relationships/relationship_manager.py
 */

import { v4 as uuidv4 } from 'uuid';
import { Person } from '../../models/Person.js';
import { Player, RelationshipData } from '../../models/Player.js';
import { createMessageEvent, EventResult } from '../../events/base.js';
import {
  checkAchievementsAsync,
  trackMarriage,
  trackDating,
  trackAffinity,
  getPlayerStatistics,
} from '../retention/index.js';

// ============================================================================
// Types and Interfaces
// ============================================================================

export type RelationshipStatus =
  | 'Dating'
  | 'Married'
  | 'Prospect'
  | 'Broke Up'
  | 'Divorced'
  | 'Friends'
  | 'Engaged';

export interface RomanticRelationship {
  id: string;
  person1: string;
  person2: string;
  startDate: string;
  relationshipStatus: RelationshipStatus;
  description: string;
  relationshipScore: number;
  eventsLog: string[];
}

export interface DateIdea {
  name: string;
  energy_cost: number;  // Python uses snake_case
  money_cost: number;   // Python uses snake_case
  message: string;
  image?: string;
}

// ============================================================================
// Relationship Class
// ============================================================================

/**
 * Create a new relationship object
 */
export function createRelationship(
  person1: string,
  person2: string,
  startDate: string,
  status: RelationshipStatus,
  description: string
): RomanticRelationship {
  return {
    id: uuidv4(),
    person1,
    person2,
    startDate,
    relationshipStatus: status,
    description,
    relationshipScore: 50,
    eventsLog: [],
  };
}

// ============================================================================
// Date Ideas
// ============================================================================

/**
 * Create a date idea
 */
export function createDateIdea(
  name: string,
  energy_cost: number,
  money_cost: number,
  message: string,
  image?: string
): DateIdea {
  return {
    name,
    energy_cost,
    money_cost,
    message,
    image,
  };
}

/**
 * Get all available date activity options
 */
export function getDateIdeas(): DateIdea[] {
  return [
    createDateIdea(
      'Picnic in the Park',
      2,
      20,
      'You enjoyed a relaxing picnic in the park.',
      'https://lichun.app/assets/images/picnicDate.png'
    ),
    createDateIdea(
      'Movie Night at Home',
      1,
      0,
      'You had a cozy movie night at home.',
      'https://lichun.app/assets/images/dateNight.png'
    ),
    createDateIdea(
      'Hiking Adventure',
      3,
      10,
      'You embarked on an exhilarating hiking adventure.',
      'https://lichun.app/assets/images/hikingDate.png'
    ),
    createDateIdea(
      'Fine Dining Experience',
      2,
      100,
      'You had a romantic fine dining experience.',
      'https://lichun.app/assets/images/fineDiningDate.png'
    ),
    createDateIdea(
      'Visit a Museum or Art Gallery',
      2,
      50,
      'You visited a museum or art gallery.',
      'https://lichun.app/assets/images/museumDate.png'
    ),
    createDateIdea(
      'Cooking Class Together',
      3,
      70,
      'You took a cooking class together.',
      'https://lichun.app/assets/images/cookingDate.png'
    ),
    createDateIdea(
      'Amusement Park Day',
      4,
      120,
      'You had a fun day at the amusement park.',
      'https://lichun.app/assets/images/amusementParkDate.png'
    ),
    createDateIdea(
      'Beach Day',
      2,
      30,
      'You spent the day at the beach.',
      'https://lichun.app/assets/images/beachDate.png'
    ),
    createDateIdea(
      'Attend a Concert or Show',
      3,
      150,
      'You attended a concert or show.',
      'https://lichun.app/assets/images/concertDate.png'
    ),
    createDateIdea(
      'Spa Day for Relaxation',
      1,
      200,
      'You enjoyed a relaxing spa day.',
      'https://lichun.app/assets/images/spaDate.png'
    ),
  ];
}

// ============================================================================
// Helper Functions
// ============================================================================

const ACTIVE_RELATIONSHIP_STATUSES: RelationshipStatus[] = [
  'Prospect',
  'Dating',
  'Engaged',
  'Married',
];

const ROMANTIC_TAGS = [
  'partner',
  'girlfriend',
  'boyfriend',
  'dating',
  'spouse',
  'wife',
  'husband',
  'fiancé',
  'fiancée',
  'engaged',
  'dating_match',
];

function isRomanticRelationship(value: unknown): value is RomanticRelationship {
  if (!value || typeof value !== 'object') return false;
  const rel = value as Record<string, unknown>;
  return (
    typeof rel.id === 'string' &&
    typeof rel.person1 === 'string' &&
    typeof rel.person2 === 'string' &&
    typeof rel.relationshipStatus === 'string'
  );
}

function getRelationshipPartner(player: Player, relationship: RomanticRelationship): Person | null {
  const partnerId = relationship.person1 === player.c.id ? relationship.person2 : relationship.person1;
  return getPerson(player, partnerId);
}

function getRelationshipIndex(player: Player, partnerId: string): number {
  return player.relData.findIndex((entry) => {
    if (!isRomanticRelationship(entry)) return false;
    return (
      (entry.person1 === player.c.id && entry.person2 === partnerId) ||
      (entry.person2 === player.c.id && entry.person1 === partnerId)
    );
  });
}

function ensurePersonRelationshipTags(person: Person, tags: string[]): void {
  person.relationships = person.relationships ?? [];
  for (const tag of tags) {
    if (!person.relationships.some((r) => r.toLowerCase() === tag.toLowerCase())) {
      person.relationships.push(tag);
    }
  }
}

function removePersonRelationshipTags(person: Person, tags: string[]): void {
  const tagSet = new Set(tags.map((t) => t.toLowerCase()));
  person.relationships = (person.relationships ?? []).filter((tag) => !tagSet.has(tag.toLowerCase()));
}

function setRelationshipReferences(player: Player, partner: Person, relationshipId: string): void {
  (player.c as unknown as { relationship: string | null }).relationship = relationshipId;
  player.c.partner = partner.id;
  (partner as unknown as { relationship: string | null }).relationship = relationshipId;
  partner.partner = player.c.id;
  ensurePersonRelationshipTags(player.c, ['partner']);
  ensurePersonRelationshipTags(partner, ['partner']);
  removePersonRelationshipTags(player.c, ['dating_match']);
  removePersonRelationshipTags(partner, ['dating_match']);
}

function clearRelationshipReferences(player: Player, partner: Person | null): void {
  (player.c as unknown as { relationship: string | null }).relationship = null;
  player.c.partner = undefined;
  removePersonRelationshipTags(player.c, ['partner', 'dating_match']);

  if (partner) {
    (partner as unknown as { relationship: string | null }).relationship = null;
    partner.partner = undefined;
    removePersonRelationshipTags(partner, ['partner', 'dating_match']);
  }
}

function finalizeRelationship(
  player: Player,
  partnerId: string,
  finalStatus: 'Broke Up' | 'Divorced',
  message: string
): boolean {
  const relationshipIndex = getRelationshipIndex(player, partnerId);
  if (relationshipIndex === -1) return false;

  const relationshipEntry = player.relData[relationshipIndex];
  if (!isRomanticRelationship(relationshipEntry)) return false;

  relationshipEntry.eventsLog = relationshipEntry.eventsLog ?? [];
  relationshipEntry.relationshipStatus = finalStatus;
  relationshipEntry.eventsLog.push(finalStatus === 'Broke Up' ? 'Relationship ended.' : 'Got divorced.');

  const partner = getPerson(player, partnerId);
  clearRelationshipReferences(player, partner);

  // Keep relData strictly for active/viewable relationships in client state.
  player.relData.splice(relationshipIndex, 1);
  player.messageQueue.push(message);
  return true;
}

/**
 * Fetch relationship data based on person1 or person2 id
 */
export function getRelData(player: Player, personId: string): RomanticRelationship | null {
  for (const rel of player.relData) {
    if (!isRomanticRelationship(rel)) {
      continue;
    }
    const relationship = rel;
    if (
      relationship.person1 === personId ||
      relationship.person2 === personId ||
      relationship.id === personId
    ) {
      return relationship;
    }
  }
  return null;
}

/**
 * Get the player's current active romantic relationship
 */
export function getActiveRelationship(player: Player): RomanticRelationship | null {
  for (const rel of player.relData) {
    if (!isRomanticRelationship(rel)) {
      continue;
    }
    const relationship = rel;
    if (ACTIVE_RELATIONSHIP_STATUSES.includes(relationship.relationshipStatus)) {
      return relationship;
    }
  }
  return null;
}

/**
 * Get a person by their ID from player's relationships
 */
function getPerson(player: Player, id: string): Person | null {
  return player.r.find((p) => p.id === id) ?? null;
}

// ============================================================================
// Affinity and Relationship Management
// ============================================================================
//
// THE THREE CLOSENESS SCALARS — roles (canonical definition).
//
// BaoLife tracks three distinct "how close are we" numbers. They are NOT
// interchangeable; conflating them was the root of the T008 audit finding that
// affinity "is tracked but never read". Their roles:
//
//   • affinity  (Person.affinity, -100..100)
//       How the NPC FEELS about the player. This is the scalar that should
//       DRIVE PAYOFF AND GATES: which conversation tools unlock, whether an NPC
//       will date you (romance() gates on affinity >= 50), whether they grant a
//       favor / lend money / vouch for a job (see services/relationships/
//       favors.ts), and whether they rally during a crisis. If a mechanic asks
//       "does this NPC like the player enough to do X?", read affinity.
//
//   • familiarity (Person.familiarity, 0..100)
//       Breadth of shared HISTORY — how much the NPC and player have been
//       through together. Drives conversation memory depth and serves only as a
//       small secondary modifier to payoffs (e.g. a tiny loan bump in
//       favors.ts). It is NOT a substitute for affinity: a long-familiar but
//       low-affinity NPC still won't go out of their way for you.
//
//   • relationshipScore (RomanticRelationship.relationshipScore, romantic only)
//       Dating-health number for an ACTIVE romance. As of T010d this is NO LONGER
//       self-correcting toward 50: a romance you tend (dates, gifts, warm chats)
//       holds or climbs, but NEGLECT — no positive interaction for N in-game days
//       — erodes the score (see applyNeglectDecay below). Sustained neglect drives
//       the score under the breakup threshold and a partner-initiated
//       breakup/divorce fires via finalizeRelationship/divorce. It remains
//       romance-specific and is NOT consulted by favor/crisis/gate logic.
//
// Rule of thumb: gate gameplay on AFFINITY, color conversations with
// FAMILIARITY, and let RELATIONSHIPSCORE govern romantic health only.

/**
 * Update the affinity score for a specific person.
 *
 * Affinity is the NPC's feeling toward the player (-100..100) and the primary
 * gameplay gate (favors, crisis support, dating eligibility, tool unlocks). See
 * the scalar-roles note above.
 */
export function updateAffinity(player: Player, id: string, value: number): Player {
  for (const person of player.r) {
    if (person.id === id) {
      const oldAffinity = person.affinity;
      person.affinity = Math.max(-100, Math.min(100, person.affinity + value));

      // Mark the high-water flag so a later neglect-decline can fire the
      // "relationship at risk" nudge.
      if (person.affinity >= 70) {
        person.affinityWasHigh = true;
      }

      // Track affinity milestone achievements
      if (person.affinity >= 100 && oldAffinity < 100) {
        trackAffinity(player.userId, person.affinity);
        checkAchievementsAsync(
          player.userId,
          'affinity_milestone',
          { affinity: person.affinity },
          {},
          {},
          player
        ).catch((err) => console.error('Error checking affinity achievements:', err));
      }
      break;
    }
  }
  return player;
}

// ============================================================================
// Neglect-decay (T010d) — replaces the old toward-50 homeostasis
// ============================================================================
//
// A romance is no longer self-correcting. Each in-game day the relationship is
// checked: if there has been no positive interaction for NEGLECT_GRACE_DAYS, the
// relationshipScore erodes (and the partner's affinity slips), modeling drifting
// apart. Sustained neglect — score under BREAKUP_SCORE_THRESHOLD for
// BREAKUP_NEGLECT_DAYS — auto-triggers a partner-initiated breakup/divorce via
// the existing finalizeRelationship/divorce path. Tending the relationship
// (dateNight, partnerGift, warm conversations) calls recordPositiveInteraction
// which resets the neglect clock, so a tended romance never auto-ends.

/** In-game days of no positive interaction before decay begins. */
export const NEGLECT_GRACE_DAYS = 7;
/** Per-day relationshipScore erosion once past the grace period. */
export const NEGLECT_SCORE_DECAY_PER_DAY = 3;
/** Per-day partner affinity erosion once past the grace period. */
export const NEGLECT_AFFINITY_DECAY_PER_DAY = 1;
/** Score at/under which a relationship is "at risk" of ending. */
export const BREAKUP_SCORE_THRESHOLD = 20;
/**
 * Once the score is at/under the threshold, the partner endures this many more
 * neglected in-game days before initiating the breakup/divorce.
 */
export const BREAKUP_NEGLECT_DAYS = NEGLECT_GRACE_DAYS + 14;

/**
 * Parse a player.date string (either "MM-DD" or "YYYY-MM-DD") into a Date in a
 * fixed reference year so day-difference math works without a real calendar.
 * Returns null for unparseable input.
 */
function parseGameDate(dateStr: string | undefined): Date | null {
  if (!dateStr) return null;
  const parts = dateStr.split('-').map((n) => parseInt(n, 10));
  if (parts.some((n) => Number.isNaN(n))) return null;
  if (parts.length >= 3) {
    // YYYY-MM-DD
    return new Date(parts[0], parts[1] - 1, parts[2]);
  }
  if (parts.length === 2) {
    // MM-DD — use a fixed NON-leap reference year so the 365-day wrap below is exact.
    return new Date(2001, parts[0] - 1, parts[1]);
  }
  return null;
}

/**
 * In-game days between two date strings. Handles a calendar wrap (the MM-DD
 * "to" date being earlier in the year than "from") by adding a year.
 * Returns 0 if either date is unparseable.
 */
export function gameDaysBetween(from: string | undefined, to: string | undefined): number {
  const a = parseGameDate(from);
  const b = parseGameDate(to);
  if (!a || !b) return 0;
  let diff = Math.round((b.getTime() - a.getTime()) / (1000 * 60 * 60 * 24));
  if (diff < 0) diff += 365; // calendar wrap for MM-DD dates
  return diff;
}

/**
 * Record a positive interaction with a partner/NPC on a given in-game date.
 * Resets the neglect clock that drives romantic decay and clears the at-risk
 * notification flag so a future decline can re-notify.
 */
export function recordPositiveInteraction(person: Person | null, date: string): void {
  if (!person) return;
  person.lastPositiveInteraction = date;
  // A genuine positive touch clears the at-risk flag once affinity is healthy.
  if ((person.affinity ?? 0) >= 60) {
    person.neglectAtRiskNotified = false;
  }
}

/**
 * Apply one in-game day of neglect-decay to the player's active romantic
 * relationship. Returns a structured result describing what happened so callers
 * (game loop) can fire notifications / messages.
 *
 * Behavior:
 *  - No active relationship → { decayed:false }.
 *  - Positive interaction within NEGLECT_GRACE_DAYS → no decay, neglect clock reset.
 *  - Otherwise erode relationshipScore + partner affinity per day.
 *  - Score at/under BREAKUP_SCORE_THRESHOLD for BREAKUP_NEGLECT_DAYS → the partner
 *    initiates a breakup (or divorce if married) via the existing finalize path.
 */
export interface NeglectDecayResult {
  decayed: boolean;
  brokeUp: boolean;
  atRisk: boolean;
  partnerId?: string;
  partnerName?: string;
  daysNeglected: number;
  relationshipScore?: number;
}

export function applyNeglectDecay(player: Player, currentDay: string): NeglectDecayResult {
  const rel = getActiveRelationship(player);
  if (!rel) {
    return { decayed: false, brokeUp: false, atRisk: false, daysNeglected: 0 };
  }

  const partner = getRelationshipPartner(player, rel);
  if (!partner || partner.status === 'dead') {
    return { decayed: false, brokeUp: false, atRisk: false, daysNeglected: 0 };
  }

  // Seed the neglect clock if it has never been set (e.g. legacy save / brand
  // new relationship). Treat the relationship start as the last positive touch.
  if (!partner.lastPositiveInteraction) {
    partner.lastPositiveInteraction = rel.startDate || currentDay;
  }

  const daysNeglected = gameDaysBetween(partner.lastPositiveInteraction, currentDay);

  // Within the grace period — the romance is being tended (or recently was).
  if (daysNeglected <= NEGLECT_GRACE_DAYS) {
    return {
      decayed: false,
      brokeUp: false,
      atRisk: false,
      partnerId: partner.id,
      partnerName: partner.firstname,
      daysNeglected,
      relationshipScore: rel.relationshipScore,
    };
  }

  // Past grace — erode score and affinity.
  rel.relationshipScore = Math.max(0, rel.relationshipScore - NEGLECT_SCORE_DECAY_PER_DAY);
  partner.affinity = Math.max(-100, (partner.affinity ?? 0) - NEGLECT_AFFINITY_DECAY_PER_DAY);

  const atRisk = rel.relationshipScore <= BREAKUP_SCORE_THRESHOLD;

  // Sustained neglect → partner-initiated breakup/divorce.
  if (atRisk && daysNeglected >= BREAKUP_NEGLECT_DAYS) {
    const married = rel.relationshipStatus === 'Married';
    const partnerName = partner.firstname;
    if (married) {
      finalizeRelationship(
        player,
        partner.id,
        'Divorced',
        `${partnerName} filed for divorce — months of neglect ended your marriage.`
      );
    } else {
      finalizeRelationship(
        player,
        partner.id,
        'Broke Up',
        `${partnerName} broke up with you after feeling neglected for too long.`
      );
    }
    return {
      decayed: true,
      brokeUp: true,
      atRisk: true,
      partnerId: partner.id,
      partnerName,
      daysNeglected,
      relationshipScore: 0,
    };
  }

  return {
    decayed: true,
    brokeUp: false,
    atRisk,
    partnerId: partner.id,
    partnerName: partner.firstname,
    daysNeglected,
    relationshipScore: rel.relationshipScore,
  };
}

/**
 * Handle relationship events and score updates for a person.
 *
 * As of T010d the old toward-50 homeostasis is gone. A random relationship
 * moment (5% chance) can still nudge the score, then neglect-decay is applied
 * for the day so an untended romance drifts and can eventually end. Tended
 * relationships (recent positive interaction) are left to climb.
 */
export function handleRelationships(player: Player, person: Person): boolean {
  const eventChance = Math.floor(Math.random() * 100) + 1;
  const rel = getRelData(player, person.id);

  if (!rel) {
    return false;
  }

  // 5% chance of random event
  if (eventChance > 95) {
    const events = ['Went on a romantic trip', 'Had a disagreement', 'Met with friends'];
    const event = events[Math.floor(Math.random() * events.length)];
    rel.eventsLog.push(event);

    if (event === 'Went on a romantic trip') {
      rel.relationshipScore += 10;
      recordPositiveInteraction(person, player.date);
    } else if (event === 'Had a disagreement') {
      rel.relationshipScore -= 10;
    } else {
      // Met with friends
      rel.relationshipScore += 5;
      recordPositiveInteraction(person, player.date);
    }
  }

  // Neglect-decay (replaces the toward-50 correction): an untended romance
  // erodes; a tended one is left alone.
  applyNeglectDecay(player, player.date);

  return true;
}

// ============================================================================
// Romantic Actions
// ============================================================================

/**
 * Check if player already has an active romantic partner
 */
export function hasActiveRomanticPartner(player: Player): Person | null {
  for (const person of player.r) {
    if (person.relationships?.some(r => ROMANTIC_TAGS.includes(r.toLowerCase()))) {
      return person;
    }
  }
  return null;
}

/**
 * Clear romantic relationship from a person (downgrade to ex)
 */
export function clearRomanticRelationship(person: Person): void {
  if (person.relationships) {
    const hadRomantic = person.relationships.some(r => ROMANTIC_TAGS.includes(r.toLowerCase()));
    person.relationships = person.relationships.filter(r => !ROMANTIC_TAGS.includes(r.toLowerCase()));

    // Add 'ex' if they had a romantic relationship
    if (hadRomantic && !person.relationships.includes('ex')) {
      person.relationships.push('ex');
    }
  }
}

/**
 * Attempt to start a romantic relationship with a target person
 */
export function romance(player: Player, partnerId: string): boolean {
  const romanticTarget = getPerson(player, partnerId);

  if (romanticTarget) {
    // Content safety: romantic relationships are adults only (18+). Block if
    // either the player or the target is under 18.
    if ((player.c.ageYears ?? 0) < 18 || (romanticTarget.ageYears ?? 0) < 18) {
      player.messageQueue.push('Dating unlocks at age 18.');
      return false;
    }

    const existingActiveRelationship = getActiveRelationship(player);
    const existingPartner = existingActiveRelationship
      ? getRelationshipPartner(player, existingActiveRelationship)
      : null;

    if (existingPartner && existingPartner.id !== partnerId) {
      player.messageQueue.push(
        `You're already in a relationship with ${existingPartner.firstname}. Break up first if you want to pursue someone else.`
      );
      return false;
    }

    // Check exclusivity - if player has an active partner, can't start new romance
    const taggedExistingPartner = hasActiveRomanticPartner(player);
    if (taggedExistingPartner && taggedExistingPartner.id !== partnerId) {
      player.messageQueue.push(
        `You're already in a relationship with ${taggedExistingPartner.firstname}. Break up first if you want to pursue someone else.`
      );
      return false;
    }

    // Check if this person likes us enough to date us (affinity >= 50)
    if (romanticTarget.affinity >= 50) {
      const relationship = createRelationship(
        player.c.id,
        romanticTarget.id,
        player.date,
        'Prospect',
        `You and ${romanticTarget.firstname} are exploring a new relationship.`
      );

      player.relData.push(relationship as unknown as RelationshipData);
      (player.c as unknown as { relationship: string }).relationship = relationship.id;
      (romanticTarget as unknown as { relationship: string }).relationship = relationship.id;
      removePersonRelationshipTags(player.c, ['partner']);
      removePersonRelationshipTags(romanticTarget, ['partner']);

      player.messageQueue.push(
        `You started seeing ${romanticTarget.firstname} ${romanticTarget.lastname}.`
      );

      // Track dating statistics
      trackDating(player.userId);
      const stats = getPlayerStatistics(player.userId);
      checkAchievementsAsync(player.userId, 'dating', {}, {}, stats, player).catch((err) =>
        console.error('Error checking dating achievements:', err)
      );

      return true;
    }
  }

  return false;
}

/**
 * Establish an active dating relationship from a mutual swipe match.
 *
 * A swipe match means both sides expressed interest, so unlike `romance()` (which
 * gates on affinity and only creates a 'Prospect'), this immediately creates a
 * 'Dating' relationship in `relData` and wires up the scalar references that the
 * client uses to render the active-romance UI and disable further swiping.
 *
 * Idempotent: if an active relationship with this partner already exists it is
 * returned unchanged. Returns the relationship that is now active, or null if the
 * player already has a different active partner (exclusivity is enforced upstream
 * for the swipe flow, but we guard here too).
 */
export function establishMatchRelationship(
  player: Player,
  partner: Person
): RomanticRelationship | null {
  // Content safety: romantic relationships are adults only (18+). Block if either
  // the player or the partner is under 18 (defense-in-depth; the swipe handler
  // also gates this).
  if ((player.c.ageYears ?? 0) < 18 || (partner.ageYears ?? 0) < 18) {
    return null;
  }

  // Already in an active relationship with this exact partner — return it.
  const existing = getRelData(player, partner.id);
  if (existing && ACTIVE_RELATIONSHIP_STATUSES.includes(existing.relationshipStatus)) {
    setRelationshipReferences(player, partner, existing.id);
    return existing;
  }

  // Don't override an existing active relationship with a *different* person.
  const otherActive = getActiveRelationship(player);
  if (otherActive) {
    const otherPartner = getRelationshipPartner(player, otherActive);
    if (otherPartner && otherPartner.id !== partner.id) {
      return null;
    }
  }

  const relationship = createRelationship(
    player.c.id,
    partner.id,
    player.date,
    'Dating',
    `You matched with ${partner.firstname} ${partner.lastname} and started dating.`
  );
  relationship.eventsLog.push('You matched on the dating app.');

  player.relData.push(relationship as unknown as RelationshipData);
  setRelationshipReferences(player, partner, relationship.id);

  // Track dating statistics / achievements, mirroring romance().
  trackDating(player.userId);
  const stats = getPlayerStatistics(player.userId);
  checkAchievementsAsync(player.userId, 'dating', {}, {}, stats, player).catch((err) =>
    console.error('Error checking dating achievements:', err)
  );

  return relationship;
}

/**
 * End a romantic relationship with a partner
 */
export function breakUp(player: Player, partnerId: string): boolean {
  return finalizeRelationship(player, partnerId, 'Broke Up', 'You broke up with your partner.');
}

/**
 * Give a gift to your partner to increase affinity
 * Costs $100 and increases affinity by 6-15 points
 */
export function partnerGift(player: Player, partnerId: string): EventResult | null {
  const relationship = getRelData(player, partnerId);
  if (
    relationship &&
    ['Dating', 'Engaged', 'Married'].includes(relationship.relationshipStatus)
  ) {
    relationship.eventsLog = relationship.eventsLog ?? [];

    // Check if player has enough money
    if (player.c.money >= 100) {
      // Subtract money
      player.c.money -= 100;

      // Calculate affinity gain
      const gained = 5 + Math.floor(Math.random() * 11);

      // Update partner affinity
      const partner = getPerson(player, partnerId);
      if (partner) {
        partner.affinity = Math.min(100, partner.affinity + gained);
      }

      relationship.eventsLog.push('Gift exchange strengthened your bond.');
      recordPositiveInteraction(partner, player.date);

      const message = `You bought a gift for your partner, gaining ${gained} affinity.`;
      player.messageQueue.push(message);

      return createMessageEvent('partnerGift', message, player, true, {
        moneyCost: 100,
        affinityChange: gained,
      });
    }
  }

  return null;
}

/**
 * Execute a date night activity with the player's partner
 */
export function dateNight(player: Player, ideaName: string): EventResult | null {
  const dateIdeas = getDateIdeas();
  const dateIdea = dateIdeas.find((idea) => idea.name === ideaName);

  if (!dateIdea) {
    return null;
  }

  // Check if player has enough energy and money
  if (player.c.energy >= dateIdea.energy_cost && player.c.money >= dateIdea.money_cost) {
    const relationship = getActiveRelationship(player);

    if (!relationship) {
      return null;
    }

    const partner = getRelationshipPartner(player, relationship);
    if (!partner) {
      return null;
    }

    // Update relationship score
    const scoreChange = Math.floor(Math.random() * 10) + 1;
    relationship.eventsLog = relationship.eventsLog ?? [];
    relationship.relationshipScore += scoreChange;
    relationship.eventsLog.push(`You went on a '${dateIdea.name}' date.`);
    recordPositiveInteraction(partner, player.date);

    // Subtract energy and money
    player.c.energy -= dateIdea.energy_cost;
    player.c.money -= dateIdea.money_cost;

    // Add prestige
    player.c.prestige += 5;

    // Create message
    let message = dateIdea.message;
    message += ` You spent ${dateIdea.energy_cost} energy and ${dateIdea.money_cost} money.`;
    message += ` Your relationship score increased by ${scoreChange} points.`;

    const successfulInteractions = relationship.eventsLog.filter((event) =>
      event.toLowerCase().includes('date')
    ).length;

    if (
      relationship.relationshipStatus === 'Prospect' &&
      (successfulInteractions >= 3 || (partner.affinity ?? 0) > 50)
    ) {
      relationship.relationshipStatus = 'Dating';
      setRelationshipReferences(player, partner, relationship.id);
      relationship.eventsLog.push('You became an official couple.');
      message += ` ${partner.firstname} is now officially your partner.`;
    }

    player.messageQueue.push(message);

    return createMessageEvent('dateNight', message, player, true, {
      energyCost: dateIdea.energy_cost,
      moneyCost: dateIdea.money_cost,
    });
  }

  return null;
}

// ============================================================================
// Relationship Status Changes
// ============================================================================

/**
 * Upgrade a relationship to engaged status
 */
export function propose(player: Player, partnerId: string): boolean {
  const rel = getRelData(player, partnerId);

  if (rel && rel.relationshipStatus === 'Dating') {
    const partner = getPerson(player, partnerId);
    if (!partner) return false;

    rel.eventsLog = rel.eventsLog ?? [];
    rel.relationshipStatus = 'Engaged';
    rel.eventsLog.push('Got engaged!');
    setRelationshipReferences(player, partner, rel.id);
    ensurePersonRelationshipTags(player.c, ['engaged']);
    ensurePersonRelationshipTags(partner, ['engaged']);
    player.messageQueue.push('You proposed and they said yes! You are now engaged.');
    return true;
  }

  return false;
}

/**
 * Upgrade a relationship to married status
 */
export function marry(player: Player, partnerId: string): boolean {
  const rel = getRelData(player, partnerId);

  if (rel && rel.relationshipStatus === 'Engaged') {
    const partner = getPerson(player, partnerId);
    if (!partner) return false;

    rel.eventsLog = rel.eventsLog ?? [];
    rel.relationshipStatus = 'Married';
    rel.eventsLog.push('Got married!');
    setRelationshipReferences(player, partner, rel.id);
    ensurePersonRelationshipTags(player.c, ['partner']);
    ensurePersonRelationshipTags(partner, ['partner']);
    player.messageQueue.push('Congratulations! You are now married.');

    // Track marriage statistics and check achievements
    trackMarriage(player.userId);
    const stats = getPlayerStatistics(player.userId);
    checkAchievementsAsync(player.userId, 'marriage', {}, {}, stats, player).catch((err) =>
      console.error('Error checking marriage achievements:', err)
    );

    return true;
  }

  return false;
}

/**
 * Divorce from a partner
 */
export function divorce(player: Player, partnerId: string): boolean {
  const rel = getRelData(player, partnerId);

  if (!rel || rel.relationshipStatus !== 'Married') {
    return false;
  }

  return finalizeRelationship(player, partnerId, 'Divorced', 'You have filed for divorce.');
}

// ============================================================================
// Relationship Queries
// ============================================================================

/**
 * Get all romantic relationships (dating, engaged, married)
 */
export function getRomanticRelationships(player: Player): RomanticRelationship[] {
  const romantic: RomanticRelationship[] = [];

  for (const rel of player.relData) {
    if (!isRomanticRelationship(rel)) {
      continue;
    }
    const relationship = rel;
    if (
      relationship.relationshipStatus === 'Dating' ||
      relationship.relationshipStatus === 'Engaged' ||
      relationship.relationshipStatus === 'Married'
    ) {
      romantic.push(relationship);
    }
  }

  return romantic;
}

/**
 * Get all past relationships (broke up, divorced)
 */
export function getPastRelationships(player: Player): RomanticRelationship[] {
  const past: RomanticRelationship[] = [];

  for (const rel of player.relData) {
    if (!isRomanticRelationship(rel)) {
      continue;
    }
    const relationship = rel;
    if (
      relationship.relationshipStatus === 'Broke Up' ||
      relationship.relationshipStatus === 'Divorced'
    ) {
      past.push(relationship);
    }
  }

  return past;
}

/**
 * Check if player is in a relationship with someone
 */
export function isInRelationship(player: Player, personId: string): boolean {
  const rel = getRelData(player, personId);
  if (!rel) return false;

  return (
    rel.relationshipStatus === 'Dating' ||
    rel.relationshipStatus === 'Engaged' ||
    rel.relationshipStatus === 'Married'
  );
}

/**
 * Get relationship status with a specific person
 */
export function getRelationshipStatus(
  player: Player,
  personId: string
): RelationshipStatus | null {
  const rel = getRelData(player, personId);
  return rel?.relationshipStatus ?? null;
}

// Export all functions
export const relationshipManager = {
  createRelationship,
  createDateIdea,
  getDateIdeas,
  getRelData,
  getActiveRelationship,
  updateAffinity,
  handleRelationships,
  applyNeglectDecay,
  recordPositiveInteraction,
  gameDaysBetween,
  romance,
  establishMatchRelationship,
  breakUp,
  partnerGift,
  dateNight,
  propose,
  marry,
  divorce,
  getRomanticRelationships,
  getPastRelationships,
  isInRelationship,
  getRelationshipStatus,
};
