import {
  pgTable,
  text,
  boolean,
  integer,
  numeric,
  timestamp,
  primaryKey,
  index,
  uniqueIndex,
} from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
import { ports } from './ports';
import { clients } from './clients';
import { berths } from './berths';
import { yachts } from './yachts';

// Pipeline stages: enquiry, qualified, nurturing, eoi, reservation, deposit_paid, contract
// (doc sub-status carried on eoi_doc_status / reservation_doc_status / contract_doc_status)

export const interests = pgTable(
  'interests',
  {
    id: text('id')
      .primaryKey()
      .$defaultFn(() => crypto.randomUUID()),
    // H-01: deleting a port is a manual super-admin operation; interests
    // shouldn't outlive their port. RESTRICT forces the operator to
    // explicitly archive/transfer interests first.
    portId: text('port_id')
      .notNull()
      .references(() => ports.id, { onDelete: 'restrict' }),
    // H-01: client is required and design intent is archive-first - the
    // service-layer hard-delete path nullifies FKs explicitly. RESTRICT
    // is a defensive backstop against an ad-hoc DB hard-delete that
    // would otherwise leave the interest pointing at a missing client.
    clientId: text('client_id')
      .notNull()
      .references(() => clients.id, { onDelete: 'restrict' }),
    yachtId: text('yacht_id').references(() => yachts.id, { onDelete: 'set null' }),
    /** Who owns this deal. Auto-assigned on create from system_settings
     *  `default_new_interest_owner`; reassignable via the interest header. */
    assignedTo: text('assigned_to'),
    pipelineStage: text('pipeline_stage').notNull().default('enquiry'),
    /** Sub-status for the doc-signing stages. NULL while the deal hasn't
     *  reached the stage yet; 'pending' | 'sent' | 'signed' | 'declined' | 'voided'. */
    eoiDocStatus: text('eoi_doc_status'),
    reservationDocStatus: text('reservation_doc_status'),
    contractDocStatus: text('contract_doc_status'),
    /** Documenso IDs per document type. EOI uses the existing `documensoId`
     *  for backward compat with the template-generate path. */
    reservationDocumensoId: text('reservation_documenso_id'),
    contractDocumensoId: text('contract_documenso_id'),
    leadCategory: text('lead_category'), // general_interest, specific_qualified, hot_lead
    source: text('source'), // website, manual, referral, broker
    eoiStatus: text('eoi_status'), // null, waiting_for_signatures, signed, expired
    documensoId: text('documenso_id'),
    contractStatus: text('contract_status'),
    depositStatus: text('deposit_status'),
    reservationStatus: text('reservation_status'),
    /** Agreed deposit amount captured at reservation-agreement time. Lets
     *  the payments running-total decide when the deposit is "fully paid"
     *  and the stage advances automatically. */
    depositExpectedAmount: numeric('deposit_expected_amount'),
    depositExpectedCurrency: text('deposit_expected_currency').default('EUR'),
    dateFirstContact: timestamp('date_first_contact', { withTimezone: true }),
    dateLastContact: timestamp('date_last_contact', { withTimezone: true }),
    dateEoiSent: timestamp('date_eoi_sent', { withTimezone: true }),
    dateEoiSigned: timestamp('date_eoi_signed', { withTimezone: true }),
    dateReservationSigned: timestamp('date_reservation_signed', { withTimezone: true }),
    dateContractSent: timestamp('date_contract_sent', { withTimezone: true }),
    dateContractSigned: timestamp('date_contract_signed', { withTimezone: true }),
    dateDepositReceived: timestamp('date_deposit_received', { withTimezone: true }),
    reminderEnabled: boolean('reminder_enabled').notNull().default(false),
    reminderDays: integer('reminder_days'),
    reminderLastFired: timestamp('reminder_last_fired', { withTimezone: true }),
    // Phase 4: optional note surfaced in the reminder notification body
    // and inbox row. NULL = use the default cadence copy.
    reminderNote: text('reminder_note'),
    /** Terminal outcome. Independent of pipelineStage - `outcome` is set
     *  alongside the stage transition to `completed` to distinguish won
     *  deals from the various lost variants. NULL while the interest is
     *  still active. */
    outcome: text('outcome'), // 'won' | 'lost_other_marina' | 'lost_unqualified' | 'lost_no_response' | 'cancelled'
    /** Free-text reason captured at the time the outcome is set. Surfaces
     *  in the timeline + reports. */
    outcomeReason: text('outcome_reason'),
    /** When the outcome was decided. Lets us age 'how long ago did we lose'. */
    outcomeAt: timestamp('outcome_at', { withTimezone: true }),
    /** Recommender inputs - dual-stored. ft is the canonical unit the
     *  recommender SQL queries on; m is the human-friendly entry the rep
     *  may have actually typed. The matching `*_unit` column says which
     *  side is source-of-truth - display prefers that side and recomputes
     *  the other so the rep's literal entry doesn't drift through repeated
     *  conversions. Resolver treats nulls as "no constraint" on that axis. */
    desiredLengthFt: numeric('desired_length_ft'),
    desiredWidthFt: numeric('desired_width_ft'),
    desiredDraftFt: numeric('desired_draft_ft'),
    desiredLengthM: numeric('desired_length_m'),
    desiredWidthM: numeric('desired_width_m'),
    desiredDraftM: numeric('desired_draft_m'),
    desiredLengthUnit: text('desired_length_unit').notNull().default('ft'),
    desiredWidthUnit: text('desired_width_unit').notNull().default('ft'),
    desiredDraftUnit: text('desired_draft_unit').notNull().default('ft'),
    /** Per-interest source-of-truth toggle for the berth recommender.
     *  When true AND a yacht is linked, the recommender uses the yacht's
     *  length/width/draft instead of the rep-entered desired_* columns
     *  on this row. Defaults false so existing data continues to use the
     *  desired-dims path. Migration 0087. */
    useYachtDimensions: boolean('use_yacht_dimensions').notNull().default(false),
    archivedAt: timestamp('archived_at', { withTimezone: true }),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (table) => [
    index('idx_interests_port').on(table.portId),
    index('idx_interests_client').on(table.clientId),
    index('idx_interests_yacht').on(table.yachtId),
    index('idx_interests_stage').on(table.portId, table.pipelineStage),
    index('idx_interests_archived')
      .on(table.portId)
      .where(sql`${table.archivedAt} IS NULL`),
    index('idx_interests_outcome').on(table.portId, table.outcome),
    index('idx_interests_assigned_to').on(table.assignedTo),
  ],
);

/**
 * Many-to-many junction between interests and berths.
 *
 * Replaces the old single-berth `interests.berth_id` column. Each row
 * carries three role flags so a rep can model "actively pitching this
 * berth" vs "covered by the EOI bundle but not pitched" vs "primary
 * berth for the deal" independently:
 *
 *   - is_primary           : at most one row per interest is the primary;
 *                            templates / forms / "the berth for this deal"
 *                            semantics resolve through this row.
 *   - is_specific_interest : true = berth shows as "Under Offer" on the
 *                            public map. false = legal/EOI-only link.
 *   - is_in_eoi_bundle     : covered by the interest's EOI signature.
 *
 * EOI bypass: when the interest has a signed primary EOI but a specific
 * berth in the bundle still needs its own EOI, a rep records the bypass
 * reason here.
 */
export const interestBerths = pgTable(
  'interest_berths',
  {
    id: text('id')
      .primaryKey()
      .$defaultFn(() => crypto.randomUUID()),
    interestId: text('interest_id')
      .notNull()
      .references(() => interests.id, { onDelete: 'cascade' }),
    berthId: text('berth_id')
      .notNull()
      .references(() => berths.id, { onDelete: 'restrict' }),
    isPrimary: boolean('is_primary').notNull().default(false),
    isSpecificInterest: boolean('is_specific_interest').notNull().default(true),
    isInEoiBundle: boolean('is_in_eoi_bundle').notNull().default(false),
    eoiBypassReason: text('eoi_bypass_reason'),
    eoiBypassedBy: text('eoi_bypassed_by'),
    eoiBypassedAt: timestamp('eoi_bypassed_at', { withTimezone: true }),
    addedBy: text('added_by'),
    addedAt: timestamp('added_at', { withTimezone: true }).notNull().defaultNow(),
    notes: text('notes'),
    // CM-2 Part B: deal-specific price for THIS (interest, berth). Null = use
    // the berth's canonical list price. Does not touch berths.price.
    priceOverride: numeric('price_override'),
    priceOverrideCurrency: text('price_override_currency'),
  },
  (table) => [
    uniqueIndex('idx_ib_interest_berth').on(table.interestId, table.berthId),
    uniqueIndex('idx_ib_one_primary')
      .on(table.interestId)
      .where(sql`${table.isPrimary} = true`),
    index('idx_ib_berth').on(table.berthId),
    index('idx_ib_specific')
      .on(table.berthId)
      .where(sql`${table.isSpecificInterest} = true`),
  ],
);

export const interestNotes = pgTable(
  'interest_notes',
  {
    id: text('id')
      .primaryKey()
      .$defaultFn(() => crypto.randomUUID()),
    interestId: text('interest_id')
      .notNull()
      .references(() => interests.id, { onDelete: 'cascade' }),
    authorId: text('author_id').notNull(), // user ID
    content: text('content').notNull(),
    mentions: text('mentions').array(), // array of mentioned user IDs
    isLocked: boolean('is_locked').notNull().default(false),
    /** Snapshot of the linked interest's pipeline_stage at note creation.
     *  Lets a rep see how the deal's notes evolved across the lifecycle
     *  (e.g. concerns raised at qualified vs after reservation). Backfill
     *  not attempted for pre-2026-05-15 rows - they stay null. */
    pipelineStageAtCreation: text('pipeline_stage_at_creation'),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (table) => [index('idx_in_interest').on(table.interestId)],
);

export const interestTags = pgTable(
  'interest_tags',
  {
    interestId: text('interest_id')
      .notNull()
      .references(() => interests.id, { onDelete: 'cascade' }),
    tagId: text('tag_id').notNull(), // references tags.id
  },
  (table) => [primaryKey({ columns: [table.interestId, table.tagId] })],
);

export type Interest = typeof interests.$inferSelect;
export type NewInterest = typeof interests.$inferInsert;
export type InterestNote = typeof interestNotes.$inferSelect;
export type NewInterestNote = typeof interestNotes.$inferInsert;
export type InterestBerth = typeof interestBerths.$inferSelect;
export type NewInterestBerth = typeof interestBerths.$inferInsert;
