/**
 * Contracts: MSA, DPA, SLA, order forms, capacity commitments.
 *
 * Two things about this market shape the design.
 *
 * First, contracts are not a closing formality on the demand side — legal sits
 * early in the pipeline, because a customer will not hand workloads to an
 * infrastructure provider before paper is in place.
 *
 * Second, and less obviously: **service levels are promised differently on each
 * side of the business**. A compute aggregator generally cannot offer a
 * conventional uptime guarantee on capacity it resells and does not control,
 * and says so publicly. What it offers self-serve customers instead is a
 * reliability tier plus a service-credits policy. Yet the same company
 * negotiates heavyweight SLAs upstream with providers, and bespoke ones
 * downstream with enterprise customers on dedicated clusters.
 *
 * All three shapes therefore coexist, and the schema holds them without
 * pretending any one is the others. See `slaKind` below.
 */
import {
  boolean,
  index,
  integer,
  jsonb,
  numeric,
  pgTable,
  text,
  timestamp,
  uuid,
} from 'drizzle-orm/pg-core';
import {
  contractStatusEnum,
  contractTypeEnum,
  slaKindEnum,
  slaMetricEnum,
} from './enums';
import { accounts } from './crm';
import { demandDeals } from './demand';
import { capacityCommitments, supplyDeals } from './supply';
import { users } from './identity';

export const contracts = pgTable(
  'contracts',
  {
    id: uuid('id').primaryKey().defaultRandom(),
    accountId: uuid('account_id')
      .notNull()
      .references(() => accounts.id, { onDelete: 'cascade' }),

    type: contractTypeEnum('type').notNull(),
    status: contractStatusEnum('status').notNull().default('draft'),

    /**
     * Which side of the market this paper governs. The same account can hold
     * both — we may buy capacity from a neocloud under one MSA while selling
     * them managed training under another.
     */
    side: text('side').notNull().default('demand'),

    title: text('title').notNull(),
    /** Counterparty's own reference, for reconciliation with their systems. */
    externalReference: text('external_reference'),

    /** Optional links to whatever this contract is about. */
    demandDealId: uuid('demand_deal_id').references(() => demandDeals.id, {
      onDelete: 'set null',
    }),
    supplyDealId: uuid('supply_deal_id').references(() => supplyDeals.id, {
      onDelete: 'set null',
    }),
    capacityCommitmentId: uuid('capacity_commitment_id').references(
      () => capacityCommitments.id,
      { onDelete: 'set null' },
    ),

    /**
     * An order form or SLA usually hangs beneath a master agreement. Modelling
     * that hierarchy means "which MSA governs this order form?" is answerable,
     * which is the first question asked in any dispute.
     *
     * Note the order of precedence in real master agreements: **the order form
     * beats the MSA**, then exhibits and addenda, then the body of the
     * agreement, then documentation. So an order-form-level override is not an
     * annotation on the master terms — it supersedes them, and must be stored
     * as data rather than left in a notes field.
     */
    parentContractId: uuid('parent_contract_id'),

    /**
     * The signing entity, where it differs from the account.
     *
     * Affiliates routinely execute their own order forms under a parent's
     * master agreement, binding themselves as if an original party. Assuming
     * one contract equals one legal entity misfiles the counterparty on
     * exactly the deals large enough to matter.
     */
    contractingPartyName: text('contracting_party_name'),

    /**
     * Take-or-pay and prepayment terms. Together with `terminationTier` below
     * these are what make a backlog figure meaningful: contracted revenue
     * under a prepaid take-or-pay commitment and contracted revenue cancellable
     * on ninety days' notice are not the same asset, and summing them
     * unweighted overstates the book.
     */
    takeOrPayFloorPct: numeric('take_or_pay_floor_pct', { precision: 6, scale: 2 }),
    prepaidPct: numeric('prepaid_pct', { precision: 6, scale: 2 }),
    /**
     * `1_prepaid`      Take-or-pay with prepayment — the highest quality
     * `2_take_or_pay`  Multi-year take-or-pay, no prepayment
     * `3_cancellable`  Monthly billing with a short termination right
     */
    terminationTier: text('termination_tier'),

    /**
     * Whether this contract may be assigned to a substitute operator on
     * default, and within how long. A negotiated step-in right can require
     * assignment of the customer agreement *and the underlying datacentre
     * agreement* within days — which only works if the link between the two
     * exists in a system somewhere. That link is `capacityCommitmentId` above.
     */
    assignableOnDefault: boolean('assignable_on_default').notNull().default(false),
    assignmentDeadlineBusinessDays: integer('assignment_deadline_business_days'),

    effectiveAt: timestamp('effective_at', { withTimezone: true }),
    expiresAt: timestamp('expires_at', { withTimezone: true }),
    executedAt: timestamp('executed_at', { withTimezone: true }),
    terminatedAt: timestamp('terminated_at', { withTimezone: true }),

    isAutoRenew: boolean('is_auto_renew').notNull().default(false),
    /** Days of notice to prevent auto-renewal. Drives the renewal alarm. */
    noticeDays: integer('notice_days'),

    valueCents: integer('value_cents'),
    currency: text('currency').notNull().default('USD'),

    governingLaw: text('governing_law'),
    /** Link to the executed document. PIG stores the pointer, not the paper. */
    documentUrl: text('document_url'),

    ownerUserId: uuid('owner_user_id').references(() => users.id, { onDelete: 'set null' }),
    notes: text('notes'),

    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (t) => [
    index('contracts_account_idx').on(t.accountId),
    index('contracts_type_idx').on(t.type),
    index('contracts_status_idx').on(t.status),
    index('contracts_expiry_idx').on(t.expiresAt),
    index('contracts_demand_deal_idx').on(t.demandDealId),
  ],
);

/**
 * The service-level terms attached to a contract.
 *
 * `slaKind` is the important column:
 *
 *   `none`            Self-serve. No service commitment whatsoever.
 *   `credits_policy`  The common case for resold capacity — a reliability tier
 *                     plus service credits when a provider fails. Not an uptime
 *                     guarantee, and must not be displayed as one.
 *   `negotiated`      A real signed SLA with committed, measurable metrics.
 *
 * Recording a credits policy as though it were a negotiated SLA would let a
 * seller promise a customer something the business has not agreed to underwrite.
 */
export const slaTerms = pgTable(
  'sla_terms',
  {
    id: uuid('id').primaryKey().defaultRandom(),
    contractId: uuid('contract_id')
      .notNull()
      .references(() => contracts.id, { onDelete: 'cascade' }),

    kind: slaKindEnum('kind').notNull().default('credits_policy'),

    /** Committed uptime, where one is genuinely committed. */
    uptimeTargetPct: numeric('uptime_target_pct', { precision: 6, scale: 3 }),
    /** Hours to replace a failed node — often more negotiated than uptime. */
    nodeReplacementHours: integer('node_replacement_hours'),
    mttrHours: integer('mttr_hours'),
    supportResponseHours: integer('support_response_hours'),

    /** 'monthly' | 'quarterly' — the window uptime is measured over. */
    measurementWindow: text('measurement_window').notNull().default('monthly'),

    /**
     * What the target is measured against: `region`, `instance`, `node`,
     * `rack`, `cluster`, `endpoint`.
     *
     * Rack-scale systems are commonly sold with TWO simultaneous levels — for
     * example 99% per node alongside 95% per rack, where a rack counts as
     * healthy at 16 of 18 nodes. Recording only one of them misstates what was
     * promised, so additional levels go in `slaMetricTargets` and this column
     * names the basis of the headline figure.
     */
    measurementUnit: text('measurement_unit').notNull().default('cluster'),

    /**
     * The remedy actually available. This is the field that matters most, and
     * the one a credits-only model gets wrong.
     *
     *   `service_credit`   A capped percentage of fees, claimed within a window
     *   `fee_abatement`    Payment obligations CANCELLED for the affected
     *                      capacity until service is restored — uncapped in
     *                      duration, and far more valuable than a credit
     *   `termination_right` The customer may exit
     *
     * Fee abatement is real and negotiated: a leading provider's filed master
     * agreement cancels fees for services not performed once a hardware
     * failure persists two consecutive business days, and does not resume them
     * until the failure is resolved.
     */
    remedyType: text('remedy_type').notNull().default('service_credit'),
    /** Consecutive duration triggering abatement, and its unit. */
    abatementTriggerValue: integer('abatement_trigger_value'),
    abatementTriggerUnit: text('abatement_trigger_unit'),

    /**
     * Deadline to file a claim, and its unit — observed across the market from
     * ten days to two billing cycles. Miss it and the credit is simply
     * forfeit, which makes this an operational alarm, not a footnote.
     */
    claimDeadlineValue: integer('claim_deadline_value'),
    claimDeadlineUnit: text('claim_deadline_unit').notNull().default('days'),
    /** Credits commonly expire if unused. */
    creditExpiryMonths: integer('credit_expiry_months'),

    /** Whether credits are the sole and exclusive remedy. Usually yes. */
    isSoleRemedy: boolean('is_sole_remedy').notNull().default(true),

    /**
     * Contractual spare-pool obligation — that the provider keeps enough
     * spares on hand to replace what cannot be remediated. Observed scoped to
     * **both compute nodes and network switches, per location**; switches are
     * routinely forgotten and are just as capable of stranding a cluster.
     */
    sparePoolObligation: text('spare_pool_obligation'),
    sparePoolScope: jsonb('spare_pool_scope').$type<string[]>().notNull().default([]),

    /**
     * Maintenance classes and their notice periods — planned, planned-critical
     * and emergency are typically distinct, with different notice and
     * different treatment in the uptime calculation. Some allowance of
     * maintenance hours is normally excluded from the measurement entirely.
     */
    maintenanceClasses: jsonb('maintenance_classes')
      .$type<
        {
          class: string;
          noticeValue: number;
          noticeUnit: string;
          allowancePerPeriodHours?: number;
          excludedFromUptime: boolean;
        }[]
      >()
      .notNull()
      .default([]),

    /**
     * Days per year during which only "reasonable endeavours" apply and the
     * service levels are suspended. A real, negotiated carve-out that can span
     * months, and one that silently voids the guarantee being sold.
     */
    reasonableEndeavoursDaysPerYear: integer('reasonable_endeavours_days_per_year'),

    /** Hours within which a root cause analysis is owed after a major incident. */
    rcaDeliveryHours: integer('rca_delivery_hours'),

    /**
     * Credit schedule: bands of missed target mapped to the percentage of fees
     * credited back. Stored as JSON because every counterparty structures these
     * differently and normalising them loses the shape of the deal.
     */
    creditSchedule: jsonb('credit_schedule')
      .$type<{ belowPct: number; creditPct: number }[]>()
      .notNull()
      .default([]),
    /** Maximum credits payable in a window, as a share of fees. */
    creditCapPct: numeric('credit_cap_pct', { precision: 6, scale: 3 }),

    /** Carve-outs: maintenance windows, force majeure, customer-caused faults. */
    exclusions: text('exclusions'),

    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (t) => [index('sla_terms_contract_idx').on(t.contractId)],
);

/** An individual committed metric, where an SLA commits several. */
export const slaMetricTargets = pgTable(
  'sla_metric_targets',
  {
    id: uuid('id').primaryKey().defaultRandom(),
    slaTermId: uuid('sla_term_id')
      .notNull()
      .references(() => slaTerms.id, { onDelete: 'cascade' }),
    metric: slaMetricEnum('metric').notNull(),
    targetValue: numeric('target_value', { precision: 12, scale: 3 }).notNull(),
    unit: text('unit'),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (t) => [index('sla_metric_targets_term_idx').on(t.slaTermId)],
);

/**
 * Dated obligations arising from a contract — renewal notice deadlines,
 * milestone deliverables, true-up dates, security reviews.
 *
 * These are what actually get missed. A contract row with an expiry date is
 * inert; an obligation with a due date can be alerted on, which is the point.
 */
export const contractObligations = pgTable(
  'contract_obligations',
  {
    id: uuid('id').primaryKey().defaultRandom(),
    contractId: uuid('contract_id')
      .notNull()
      .references(() => contracts.id, { onDelete: 'cascade' }),

    title: text('title').notNull(),
    description: text('description'),
    /** 'renewal_notice' | 'milestone' | 'payment' | 'review' | 'true_up' */
    kind: text('kind').notNull().default('milestone'),

    dueAt: timestamp('due_at', { withTimezone: true }).notNull(),
    completedAt: timestamp('completed_at', { withTimezone: true }),
    ownerUserId: uuid('owner_user_id').references(() => users.id, { onDelete: 'set null' }),

    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  (t) => [
    index('contract_obligations_contract_idx').on(t.contractId),
    index('contract_obligations_due_idx').on(t.dueAt),
    index('contract_obligations_owner_idx').on(t.ownerUserId),
  ],
);

export type Contract = typeof contracts.$inferSelect;
export type NewContract = typeof contracts.$inferInsert;
export type SlaTerm = typeof slaTerms.$inferSelect;
export type ContractObligation = typeof contractObligations.$inferSelect;
