import { asc, desc, eq, inArray } from 'drizzle-orm';
import type { Contract, Database, SlaTerm } from '@pig/db';
import {
  accounts,
  contractObligations,
  contracts,
  slaMetricTargets,
  slaTerms,
} from '@pig/db';

export interface EffectiveTerm<Value = unknown> {
  value: Value;
  sourceContractId: string;
  inherited: boolean;
}

export type EffectiveTerms = Record<string, EffectiveTerm>;

const CONTRACT_TERM_FIELDS = [
  'contractingPartyName',
  'takeOrPayFloorPct',
  'prepaidPct',
  'terminationTier',
  'assignableOnDefault',
  'assignmentDeadlineBusinessDays',
  'effectiveAt',
  'expiresAt',
  'isAutoRenew',
  'noticeDays',
  'valueCents',
  'currency',
  'governingLaw',
] as const satisfies readonly (keyof Contract)[];

const SLA_TERM_FIELDS = [
  'kind',
  'uptimeTargetPct',
  'nodeReplacementHours',
  'mttrHours',
  'supportResponseHours',
  'measurementWindow',
  'measurementUnit',
  'remedyType',
  'abatementTriggerValue',
  'abatementTriggerUnit',
  'claimDeadlineValue',
  'claimDeadlineUnit',
  'creditExpiryMonths',
  'isSoleRemedy',
  'sparePoolObligation',
  'sparePoolScope',
  'maintenanceClasses',
  'reasonableEndeavoursDaysPerYear',
  'rcaDeliveryHours',
  'creditSchedule',
  'creditCapPct',
  'exclusions',
] as const satisfies readonly (keyof SlaTerm)[];

/**
 * Resolves explicit child terms before walking toward the master agreement.
 * Null means "not negotiated here"; false, zero and empty arrays are explicit
 * values and therefore must not accidentally fall through to a parent.
 */
export function resolveContractPrecedence(
  selectedId: string,
  contractRows: readonly Contract[],
  slaRows: readonly SlaTerm[],
): { chain: Contract[]; contract: EffectiveTerms; sla: EffectiveTerms } {
  const byId = new Map(contractRows.map((row) => [row.id, row]));
  const slaByContract = new Map(slaRows.map((row) => [row.contractId, row]));
  const chain: Contract[] = [];
  const visited = new Set<string>();
  let cursor = byId.get(selectedId);

  while (cursor && !visited.has(cursor.id)) {
    chain.push(cursor);
    visited.add(cursor.id);
    cursor = cursor.parentContractId ? byId.get(cursor.parentContractId) : undefined;
  }

  const contract: EffectiveTerms = {};
  const sla: EffectiveTerms = {};
  for (const [index, row] of chain.entries()) {
    for (const field of CONTRACT_TERM_FIELDS) {
      const value = row[field];
      if (!(field in contract) && value !== null && value !== undefined) {
        contract[field] = { value, sourceContractId: row.id, inherited: index > 0 };
      }
    }

    const serviceLevel = slaByContract.get(row.id);
    if (!serviceLevel) continue;
    for (const field of SLA_TERM_FIELDS) {
      const value = serviceLevel[field];
      if (!(field in sla) && value !== null && value !== undefined) {
        sla[field] = { value, sourceContractId: row.id, inherited: index > 0 };
      }
    }
  }

  return { chain, contract, sla };
}

export type RenewalState = 'not_applicable' | 'scheduled' | 'due' | 'expired';

export function renewalAlarm(
  contract: Pick<Contract, 'expiresAt' | 'isAutoRenew' | 'noticeDays'>,
  now = new Date(),
): { renewalNoticeAt: Date | null; renewalState: RenewalState } {
  if (!contract.isAutoRenew || !contract.expiresAt || contract.noticeDays == null) {
    return { renewalNoticeAt: null, renewalState: 'not_applicable' };
  }
  const renewalNoticeAt = new Date(
    contract.expiresAt.getTime() - contract.noticeDays * 24 * 60 * 60 * 1000,
  );
  const renewalState =
    contract.expiresAt <= now ? 'expired' : renewalNoticeAt <= now ? 'due' : 'scheduled';
  return { renewalNoticeAt, renewalState };
}

export function validateParentRelationship(
  child: Pick<Contract, 'id' | 'accountId' | 'side'>,
  parent: Pick<Contract, 'id' | 'accountId' | 'side'>,
  ancestors: readonly Pick<Contract, 'id'>[],
): string | null {
  if (child.id === parent.id || ancestors.some((ancestor) => ancestor.id === child.id)) {
    return 'A contract cannot be its own ancestor.';
  }
  if (child.accountId !== parent.accountId) {
    return 'Parent and child contracts must belong to the same account.';
  }
  if (child.side !== parent.side) {
    return 'Parent and child contracts must govern the same market side.';
  }
  return null;
}

export class ContractService {
  constructor(private readonly db: Database) {}

  async list() {
    const rows = await this.db
      .select({ contract: contracts, accountName: accounts.name })
      .from(contracts)
      .leftJoin(accounts, eq(accounts.id, contracts.accountId))
      .orderBy(desc(contracts.updatedAt));

    return rows.map((row) => ({
      ...row,
      ...renewalAlarm(row.contract),
    }));
  }

  async detail(id: string) {
    const [selected] = await this.db
      .select({ contract: contracts, accountName: accounts.name })
      .from(contracts)
      .leftJoin(accounts, eq(accounts.id, contracts.accountId))
      .where(eq(contracts.id, id))
      .limit(1);
    if (!selected) return null;

    const accountContracts = await this.db
      .select()
      .from(contracts)
      .where(eq(contracts.accountId, selected.contract.accountId))
      .orderBy(asc(contracts.createdAt));
    const contractIds = accountContracts.map((row) => row.id);
    const serviceLevels = contractIds.length
      ? await this.db.select().from(slaTerms).where(inArray(slaTerms.contractId, contractIds))
      : [];
    const termIds = serviceLevels.map((row) => row.id);

    const [metrics, obligations] = await Promise.all([
      termIds.length
        ? this.db
            .select()
            .from(slaMetricTargets)
            .where(inArray(slaMetricTargets.slaTermId, termIds))
        : Promise.resolve([]),
      this.db
        .select()
        .from(contractObligations)
        .where(eq(contractObligations.contractId, id))
        .orderBy(asc(contractObligations.dueAt)),
    ]);

    const precedence = resolveContractPrecedence(id, accountContracts, serviceLevels);
    const selectedSla = serviceLevels.find((row) => row.contractId === id) ?? null;

    return {
      ...selected,
      ...renewalAlarm(selected.contract),
      hierarchy: {
        chain: precedence.chain,
        children: accountContracts.filter((row) => row.parentContractId === id),
      },
      effectiveTerms: { contract: precedence.contract, sla: precedence.sla },
      sla: selectedSla,
      slaMetrics: selectedSla
        ? metrics.filter((row) => row.slaTermId === selectedSla.id)
        : [],
      obligations,
    };
  }
}
