import {
  evaluateCustomerLifecycle,
  type CustomerLifecycleProjection,
  type LifecycleAllocationSnapshot,
  type LifecycleContractSnapshot,
  type LifecycleDealSnapshot,
  type LifecycleRequestSnapshot,
} from '@pig/core';
import {
  accounts,
  activities,
  allocations,
  capacityRequests,
  contractObligations,
  contracts,
  demandDeals,
  type Database,
} from '@pig/db';
import { and, desc, eq, inArray, isNull, or } from 'drizzle-orm';
import { CapacityService } from './capacity';

export interface GrowthCustomer {
  account: {
    id: string;
    name: string;
    domain: string | null;
    customerSegment: string | null;
    ownerUserId: string | null;
  };
  lifecycle: CustomerLifecycleProjection;
  openDealCount: number;
}

export interface GrowthIdleSupply {
  commitmentId: string;
  name: string;
  gpuType: string;
  gpuCount: number;
  startsAt: Date;
  endsAt: Date;
  soldGpuHours: number;
  heldGpuHours: number;
  availableGpuHours: number;
  idleGpuHours: number;
  idleCostCents: number;
  breakEvenPriceCents: number | null;
}

export interface GrowthReport {
  rulesetVersion: string;
  computedAt: string;
  customers: GrowthCustomer[];
  customersTruncated: boolean;
  idleSupply: GrowthIdleSupply[];
}

export class CustomerLifecycleService {
  private readonly capacity: CapacityService;

  constructor(
    private readonly db: Database,
    private readonly clock: () => Date = () => new Date(),
  ) {
    this.capacity = new CapacityService(db);
  }

  async report(accountId?: string): Promise<GrowthReport> {
    const now = this.clock();
    const accountRows = await this.db
      .select({
        id: accounts.id,
        name: accounts.name,
        domain: accounts.domain,
        customerSegment: accounts.customerSegment,
        ownerUserId: accounts.ownerUserId,
        lastActivityAt: accounts.lastActivityAt,
      })
      .from(accounts)
      .where(and(
        or(eq(accounts.side, 'demand'), eq(accounts.side, 'both')),
        isNull(accounts.archivedAt),
        accountId ? eq(accounts.id, accountId) : undefined,
      ))
      .orderBy(desc(accounts.updatedAt))
      .limit(accountId ? 1 : 201);
    const customersTruncated = accountRows.length > 200;
    if (customersTruncated) accountRows.length = 200;
    const accountIds = accountRows.map((account) => account.id);
    if (!accountIds.length) {
      const idleSupply = await this.readIdleSupply();
      return {
        rulesetVersion: 'growth-r1-2026-08-13',
        computedAt: now.toISOString(),
        customers: [],
        customersTruncated: false,
        idleSupply,
      };
    }

    const [dealRows, contractRows, activityRows] = await Promise.all([
      this.db.select().from(demandDeals).where(inArray(demandDeals.accountId, accountIds)),
      this.db.select().from(contracts).where(and(inArray(contracts.accountId, accountIds), eq(contracts.side, 'demand'))),
      this.db.select().from(activities).where(inArray(activities.accountId, accountIds)).orderBy(desc(activities.occurredAt)).limit(2_000),
    ]);
    const dealIds = dealRows.map((deal) => deal.id);
    const contractIds = contractRows.map((contract) => contract.id);
    const [requestRows, allocationRows, obligationRows, idleSupply] = await Promise.all([
      dealIds.length ? this.db.select().from(capacityRequests).where(inArray(capacityRequests.demandDealId, dealIds)) : [],
      dealIds.length ? this.db.select().from(allocations).where(inArray(allocations.demandDealId, dealIds)) : [],
      contractIds.length ? this.db.select().from(contractObligations).where(inArray(contractObligations.contractId, contractIds)) : [],
      this.readIdleSupply(),
    ]);

    const customers = accountRows.map((account): GrowthCustomer => {
      const deals = dealRows.filter((deal) => deal.accountId === account.id);
      const ownDealIds = new Set(deals.map((deal) => deal.id));
      const ownContracts = contractRows.filter((contract) => contract.accountId === account.id);
      const ownContractIds = new Set(ownContracts.map((contract) => contract.id));
      const recentActivity = activityRows.find((activity) => activity.accountId === account.id);
      const lifecycle = evaluateCustomerLifecycle({
        accountId: account.id,
        deals: deals.map((deal): LifecycleDealSnapshot => ({
          id: deal.id,
          stage: deal.stage,
          productLine: deal.productLine,
          parentDealId: deal.parentDealId,
          msaExecuted: deal.msaExecuted,
          lastActivityAt: deal.lastActivityAt,
        })),
        requests: requestRows
          .filter((request) => ownDealIds.has(request.demandDealId))
          .map((request): LifecycleRequestSnapshot => ({
            id: request.id,
            demandDealId: request.demandDealId,
            startsAt: request.startsAt,
            endsAt: request.endsAt,
            totalGpuHours: request.totalGpuHours == null ? null : Number(request.totalGpuHours),
          })),
        allocations: allocationRows
          .filter((allocation) => allocation.demandDealId && ownDealIds.has(allocation.demandDealId))
          .map((allocation): LifecycleAllocationSnapshot => ({
            id: allocation.id,
            demandDealId: allocation.demandDealId,
            status: allocation.status,
            gpuHours: Number(allocation.gpuHours),
            startsAt: allocation.startsAt,
            endsAt: allocation.endsAt,
            holdExpiresAt: allocation.holdExpiresAt,
          })),
        contracts: ownContracts.map((contract): LifecycleContractSnapshot => ({
          id: contract.id,
          status: contract.status,
          expiresAt: contract.expiresAt,
          isAutoRenew: contract.isAutoRenew,
          noticeDays: contract.noticeDays,
        })),
        obligations: obligationRows.filter((obligation) => ownContractIds.has(obligation.contractId)),
        lastActivityAt: recentActivity?.occurredAt ?? account.lastActivityAt,
        lastActivityId: recentActivity?.id,
      }, now);
      return {
        account: {
          id: account.id,
          name: account.name,
          domain: account.domain,
          customerSegment: account.customerSegment,
          ownerUserId: account.ownerUserId,
        },
        lifecycle,
        openDealCount: deals.filter((deal) => !['closed_won', 'closed_lost'].includes(deal.stage)).length,
      };
    }).sort((left, right) =>
      right.lifecycle.score - left.lifecycle.score || left.account.name.localeCompare(right.account.name),
    );

    return {
      rulesetVersion: customers[0]?.lifecycle.rulesetVersion ?? 'growth-r1-2026-08-13',
      computedAt: now.toISOString(),
      customers,
      customersTruncated,
      idleSupply,
    };
  }

  async account(accountId: string): Promise<GrowthCustomer | null> {
    const report = await this.report(accountId);
    return report.customers.find((customer) => customer.account.id === accountId) ?? null;
  }

  private async readIdleSupply(): Promise<GrowthIdleSupply[]> {
    const rows = await this.capacity.idleCapacity({ thresholdPct: 0.25, withinDays: 30 });
    return rows.map((row) => ({
      commitmentId: row.commitmentId,
      name: row.name,
      gpuType: row.gpuType,
      gpuCount: row.gpuCount,
      startsAt: row.startsAt,
      endsAt: row.endsAt,
      soldGpuHours: row.soldGpuHours,
      heldGpuHours: row.heldGpuHours,
      availableGpuHours: row.availableGpuHours,
      idleGpuHours: row.idleGpuHours,
      idleCostCents: row.idleCostCents,
      breakEvenPriceCents: row.breakEvenPriceCents,
    }));
  }
}
