import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm';

import { Company } from '../../companies/entities/company.entity';
import { CompanyProfile } from '../../companies/entities/company-profile.entity';
import { ServiceType } from '../../rule-engine/entities/service-type.entity';
import { FileRecord } from '../../files/entities/file.entity';
import { ContractRoute } from './contract-route.entity';
import { ContractCargoScope } from './contract-cargo-scope.entity';
import { ContractRateSnapshot } from './contract-rate-snapshot.entity';
import { ContractSignature } from './contract-signature.entity';
import { ContractApprovalStep } from './contract-approval-step.entity';
import { ContractReviewNote } from './contract-review-note.entity';
import { ContractClearanceCycle } from './contract-clearance-cycle.entity';

export const CONTRACT_STATUSES = [
  'DRAFT',
  'SUBMITTED',
  'PRICE_CHANGED_PENDING_CONFIRM',
  'CHANGES_REQUESTED',
  'PENDING_APPROVAL',
  'APPROVED',
  'APPROVED_PENDING_SIGNATURE',
  'CONTRACT_READY',
  'SIGNED_CUSTOMER',
  'FULLY_EXECUTED',
  'CONTRACT_ACTIVE',
  'AWAITING_CLEARANCE_DOCUMENTS',
  'CLEARANCE_UNDER_REVIEW',
  'CLEARANCE_READY_FOR_BOOKING',
  'ACTIVE_SHIPMENT_IN_PROGRESS',
  'CONTRACT_CLOSED',
  'EXPIRED',
  'REJECTED',
  'CANCELLED',
  'RENEWAL_DRAFT',
  'RENEWAL_SUBMITTED',
  'RENEWAL_PENDING_APPROVAL',
  'AMENDMENTS_PROPOSED',
  'ARCHIVED',
] as const;

export type ContractStatus = (typeof CONTRACT_STATUSES)[number];

/** One article on a per-contract document snapshot (mirrors the template shape). */
export interface ContractDocumentArticle {
  id: string;
  title: string;
  body: string;
  order: number;
}

/**
 * A per-contract copy of the resolved contract-document template, frozen when
 * staff accept the contract for approval. Staff may edit these articles for a
 * single contract in the accept/edit dialog — editing NEVER writes back to the
 * shared six {@link ContractTemplate} rows. The PDF is rendered from this
 * snapshot when present; a null snapshot renders from the live template.
 */
export interface ContractDocumentSnapshot {
  code?: string | null;
  name?: string | null;
  documentTitle?: string | null;
  whereasClauses: string[];
  articles: ContractDocumentArticle[];
}

/** Loose inbound shape (article ids/order optional) — normalized before store. */
export interface ContractDocumentSnapshotInput {
  code?: string | null;
  name?: string | null;
  documentTitle?: string | null;
  whereasClauses?: string[];
  articles?: Array<{
    id?: string;
    title?: string;
    body?: string;
    order?: number;
  }>;
}

export const CONTRACT_KINDS = ['ONE_TIME', 'GENERAL'] as const;
export type ContractKindValue = (typeof CONTRACT_KINDS)[number];

export const CONTRACT_CLEARANCE_STATUSES = [
  'NOT_APPLICABLE',
  'AWAITING_DOCUMENTS',
  'DOCUMENTS_UNDER_REVIEW',
  'CLEARANCE_READY_FOR_BOOKING', // Path B — GL may create the booking
  'SELF_CLEARED', // Path A — Operations approved self-clearance; customer may book
  'ACTIVE_SHIPMENT_IN_PROGRESS',
] as const;
export type ContractClearanceStatusValue =
  (typeof CONTRACT_CLEARANCE_STATUSES)[number];

/** Statuses where the customer may still edit contract fields. */
export const CONTRACT_CUSTOMER_EDITABLE_STATUSES: ContractStatus[] = [
  'DRAFT',
  'CHANGES_REQUESTED',
];

/**
 * The legal/commercial agreement. Defines what cargo sizes/commodities, routes,
 * and flags are in scope plus the frozen unit rates — but NO quantities. Spawns
 * shipment {@link Booking} rows via bookings.contract_id. See docs/new-doc.md §5.2.
 */
@Entity({ schema: 'freight', name: 'contracts' })
@Index(['companyId'])
@Index(['status'])
@Index(['contractKind'])
export class Contract extends BaseEntity {
  @Column({ name: 'reference', type: 'varchar', length: 64, unique: true })
  reference!: string;

  @Column({ name: 'company_id', type: 'uuid', nullable: true })
  companyId?: string | null;

  @ManyToOne(() => Company, { nullable: true })
  @JoinColumn({ name: 'company_id' })
  company?: Company | null;

  @Column({ name: 'company_profile_id', type: 'uuid', nullable: true })
  companyProfileId?: string | null;

  @ManyToOne(() => CompanyProfile, { nullable: true })
  @JoinColumn({ name: 'company_profile_id' })
  companyProfile?: CompanyProfile | null;

  @Column({ name: 'is_government', type: 'boolean', default: false })
  isGovernment!: boolean;

  @Column({ name: 'government_institution', type: 'varchar', length: 255, nullable: true })
  governmentInstitution?: string | null;

  @Column({ name: 'contract_kind', type: 'varchar', length: 20 })
  contractKind!: ContractKindValue;

  @Column({ name: 'renewal_of_id', type: 'uuid', nullable: true })
  renewalOfId?: string | null;

  @ManyToOne(() => Contract, { nullable: true })
  @JoinColumn({ name: 'renewal_of_id' })
  renewalOf?: Contract | null;

  @Column({ name: 'trade_direction', type: 'varchar', length: 10 })
  tradeDirection!: string;

  @Column({ name: 'freight_type', type: 'varchar', length: 20 })
  freightType!: string;

  @Column({ name: 'service_type_id', type: 'uuid' })
  serviceTypeId!: string;

  @ManyToOne(() => ServiceType)
  @JoinColumn({ name: 'service_type_id' })
  serviceType?: ServiceType;

  @Column({ name: 'payment_currency', type: 'varchar', length: 5 })
  paymentCurrency!: string;

  @Column({ name: 'customs_clearing_enabled', type: 'boolean', default: false })
  customsClearingEnabled!: boolean;

  @Column({ name: 'customs_clearing_agent', type: 'varchar', length: 200, nullable: true })
  customsClearingAgent?: string | null;

  @Column({ name: 'equipment_return', type: 'varchar', length: 20, nullable: true })
  equipmentReturn?: string | null;

  @Column({ name: 'first_mile_pickup_address', type: 'text', nullable: true })
  firstMilePickupAddress?: string | null;

  @Column({ name: 'first_mile_pickup_lat', type: 'numeric', precision: 10, scale: 7, nullable: true })
  firstMilePickupLat?: number | null;

  @Column({ name: 'first_mile_pickup_lng', type: 'numeric', precision: 10, scale: 7, nullable: true })
  firstMilePickupLng?: number | null;

  @Column({ name: 'last_mile_delivery_address', type: 'text', nullable: true })
  lastMileDeliveryAddress?: string | null;

  @Column({ name: 'last_mile_delivery_lat', type: 'numeric', precision: 10, scale: 7, nullable: true })
  lastMileDeliveryLat?: number | null;

  @Column({ name: 'last_mile_delivery_lng', type: 'numeric', precision: 10, scale: 7, nullable: true })
  lastMileDeliveryLng?: number | null;

  @Column({ name: 'is_hazardous', type: 'boolean', default: false })
  isHazardous!: boolean;

  /** UN/ADR dangerous-goods class (CLASS_1..CLASS_9); null unless hazardous. */
  @Column({ name: 'hazard_class', type: 'varchar', length: 16, nullable: true })
  hazardClass?: string | null;

  /** UN number of the dangerous good; null unless hazardous. */
  @Column({ name: 'un_number', type: 'varchar', length: 16, nullable: true })
  unNumber?: string | null;

  @Column({ name: 'is_reefer', type: 'boolean', default: false })
  isReefer!: boolean;

  @Column({ name: 'estimated_shipment_date', type: 'timestamptz', nullable: true })
  estimatedShipmentDate?: Date | null;

  @Column({ name: 'contract_validity_days', type: 'int', nullable: true })
  contractValidityDays?: number | null;

  @Column({ name: 'contract_valid_from', type: 'timestamptz', nullable: true })
  contractValidFrom?: Date | null;

  @Column({ name: 'contract_valid_until', type: 'timestamptz', nullable: true })
  contractValidUntil?: Date | null;

  @Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
  expiresAt?: Date | null;

  @Column({ name: 'status', type: 'varchar', length: 40, default: 'DRAFT' })
  status!: string;

  @Column({ name: 'clearance_status', type: 'varchar', length: 40, default: 'NOT_APPLICABLE' })
  clearanceStatus!: string;

  @Column({ name: 'clearance_cycle_number', type: 'int', default: 0 })
  clearanceCycleNumber!: number;

  @Column({ name: 'pricing_breakdown', type: 'jsonb', nullable: true })
  pricingBreakdown?: Record<string, unknown> | null;

  @Column({ name: 'pricing_display_mode', type: 'varchar', length: 20, default: 'UNIT_RATES', nullable: true })
  pricingDisplayMode?: string | null;

  @Column({ name: 'contract_type', type: 'varchar', length: 20, nullable: true })
  contractType?: string | null;

  @Column({ name: 'contract_template_key', type: 'varchar', length: 128, nullable: true })
  contractTemplateKey?: string | null;

  @Column({ name: 'contract_generated_at', type: 'timestamptz', nullable: true })
  contractGeneratedAt?: Date | null;

  /**
   * Per-contract frozen copy of the document template (articles + WHEREAS),
   * captured at staff accept. Editing it affects only this contract, never the
   * shared six templates. Null → the PDF renders from the live template.
   */
  @Column({ name: 'document_snapshot', type: 'jsonb', nullable: true })
  documentSnapshot?: ContractDocumentSnapshot | null;

  @Column({ name: 'contract_summary', type: 'text', nullable: true })
  contractSummary?: string | null;

  @Column({ name: 'version_number', type: 'int', default: 1 })
  versionNumber!: number;

  @Column({ name: 'financial_terms', type: 'jsonb', nullable: true })
  financialTerms?: Record<string, unknown> | null;

  @Column({ name: 'approved_by_staff_id', type: 'uuid', nullable: true })
  approvedByStaffId?: string | null;

  @Column({ name: 'approved_by_staff_at', type: 'timestamptz', nullable: true })
  approvedByStaffAt?: Date | null;

  @Column({ name: 'signed_by_director_id', type: 'uuid', nullable: true })
  signedByDirectorId?: string | null;

  @Column({ name: 'signed_by_director_at', type: 'timestamptz', nullable: true })
  signedByDirectorAt?: Date | null;

  @Column({ name: 'signed_by_ceo_id', type: 'uuid', nullable: true })
  signedByCeoId?: string | null;

  @Column({ name: 'signed_by_ceo_at', type: 'timestamptz', nullable: true })
  signedByCeoAt?: Date | null;

  @Column({ name: 'customer_signed_at', type: 'timestamptz', nullable: true })
  customerSignedAt?: Date | null;

  @Column({ name: 'fully_executed_at', type: 'timestamptz', nullable: true })
  fullyExecutedAt?: Date | null;

  @Column({ name: 'locked_at', type: 'timestamptz', nullable: true })
  lockedAt?: Date | null;

  @OneToMany(() => ContractRoute, (r) => r.contract)
  routes?: ContractRoute[];

  @OneToMany(() => ContractCargoScope, (c) => c.contract)
  cargoScope?: ContractCargoScope[];

  @OneToMany(() => ContractRateSnapshot, (s) => s.contract)
  rateSnapshots?: ContractRateSnapshot[];

  @OneToMany(() => ContractSignature, (s) => s.contract)
  signatures?: ContractSignature[];

  @OneToMany(() => ContractApprovalStep, (s) => s.contract)
  approvalSteps?: ContractApprovalStep[];

  @OneToMany(() => ContractReviewNote, (n) => n.contract)
  reviewNotes?: ContractReviewNote[];

  @OneToMany(() => ContractClearanceCycle, (c) => c.contract)
  clearanceCycles?: ContractClearanceCycle[];

  @OneToMany(() => FileRecord, (file) => file.resourceId, {
    createForeignKeyConstraints: false,
  })
  files?: FileRecord[];

  /**
   * Latest clearance cycle's current_phase, attached by
   * ContractsRepository.attachClearancePhases for list responses. Not a column.
   */
  clearancePhase?: string | null;

  /**
   * Latest clearance cycle's linked booking (id + status), attached alongside
   * clearancePhase. Lets the GL queue tell an expired (unpaid) booking apart
   * from a live one so it can offer a rebook. Not columns.
   */
  latestCycleBookingId?: string | null;
  latestCycleBookingStatus?: string | null;

  /**
   * Body of the most recent CHANGES_REQUESTED review note, attached by
   * ContractsService.findById so the portal can show the customer what staff
   * asked them to fix. Lives in contract_review_notes, not a column here.
   */
  latestChangeRequestNote?: string | null;

  /**
   * Body of the most recent REJECTION review note, attached by
   * ContractsService.findById when status is REJECTED so both backoffice and
   * portal can show why. Lives in contract_review_notes, not a column here.
   */
  latestRejectionNote?: string | null;

  /**
   * Body of the most recent send-back STAFF_NOTE, attached by
   * ContractsService.findById while the contract is PENDING_APPROVAL and no
   * approval step has acted since the send-back. Lives in
   * contract_review_notes, not a column here.
   */
  latestSendBackNote?: string | null;
}
