import {
  CONSUMING_ALLOCATION_STATUSES,
  GPU_SOCKETS,
  GUARANTEE_TYPES,
  INTERCONNECT_TYPES,
  SECURITY_TIERS,
} from '@pig/core';
import type { Allocation, CapacityCommitment, Database } from '@pig/db';
import { Hono } from 'hono';
import { z } from 'zod';
import type { ApiEnv } from '../lib/mutation';
import type { MutationDefinition } from '../lib/mutation';
import { MutationError, mutation } from '../lib/mutation';
import {
  CapacityWriteService,
  type CapacityWriteTransaction,
} from '../services/capacity-writes';

const uuid = z.string().uuid();
const cents = z.number().int().nonnegative();
const percentage = z.number().min(0).max(100);
const gpuHours = z.number().positive().refine(
  (value) => Math.abs(value * 100 - Math.round(value * 100)) < 1e-7,
  'GPU-hours may have at most two decimal places.',
);
const currency = z.string().regex(/^[A-Z]{3}$/);
const shape = z
  .object({
    intervals: z.array(z.string().datetime()).min(2),
    quantities: z.array(z.number().int().nonnegative()).min(1),
  })
  .strict();

const commitmentFields = {
  accountId: uuid,
  siteId: uuid.nullable().optional(),
  supplyDealId: uuid.nullable().optional(),
  name: z.string().trim().min(1).max(200),
  gpuType: z.string().trim().min(1).max(100),
  socket: z.enum(GPU_SOCKETS).nullable().optional(),
  gpuCount: z.number().int().positive(),
  interconnectType: z.enum(INTERCONNECT_TYPES).optional(),
  securityTier: z.enum(SECURITY_TIERS).optional(),
  startsAt: z.string().datetime(),
  endsAt: z.string().datetime(),
  totalGpuHours: gpuHours,
  costPerGpuHourCents: cents,
  currency: currency.optional(),
  shape: shape.nullable().optional(),
  colocateWith: z.array(uuid).max(100).optional(),
  isContiguous: z.boolean().optional(),
  minimumSpendCents: cents.nullable().optional(),
  isAutoRenew: z.boolean().optional(),
  noticeDays: z.number().int().nonnegative().nullable().optional(),
  takeOrPayFloorPct: percentage.nullable().optional(),
  prepaidPct: percentage.nullable().optional(),
  prepaidAmountCents: cents.nullable().optional(),
  usefulLifeYears: z.number().positive().max(100).nullable().optional(),
  salvageValuePct: percentage.nullable().optional(),
  depreciationStartAt: z.string().datetime().nullable().optional(),
  costOfCapitalBps: z.number().int().nonnegative().nullable().optional(),
  financingInstrument: z.string().trim().min(1).max(200).nullable().optional(),
  oversubscriptionPct: z.number().min(0).max(1000).optional(),
  notes: z.string().max(10_000).nullable().optional(),
};

const createCommitmentSchema = z.object(commitmentFields).strict();
const updateCommitmentSchema = z
  .object({
    ...commitmentFields,
    terminatedAt: z.string().datetime().nullable().optional(),
  })
  .partial()
  .strict()
  .refine((input) => Object.keys(input).length > 0, 'At least one change is required.');

const allocationFields = {
  capacityCommitmentId: uuid,
  demandDealId: uuid,
  gpuHours,
  pricePerGpuHourCents: cents,
  currency: currency.optional(),
  startsAt: z.string().datetime(),
  endsAt: z.string().datetime(),
  guaranteeType: z.enum(GUARANTEE_TYPES).optional(),
  priority: z.number().int().nonnegative().optional(),
  complianceDecisionId: uuid.nullable().optional(),
  notes: z.string().max(10_000).nullable().optional(),
};

const createAllocationSchema = z
  .object({
    ...allocationFields,
    status: z.enum(CONSUMING_ALLOCATION_STATUSES),
  })
  .strict();
const createHoldSchema = z
  .object({
    ...allocationFields,
    pricePerGpuHourCents: cents.optional(),
    holdExpiresAt: z.string().datetime(),
    holdOpportunityCostCents: cents.nullable().optional(),
  })
  .strict();
const releaseSchema = z
  .object({ reason: z.string().trim().min(1).max(1_000).optional() })
  .strict();

type CapacityWriteOperations = Pick<
  CapacityWriteService,
  | 'createCommitment'
  | 'updateCommitment'
  | 'createAllocation'
  | 'createHold'
  | 'releaseAllocation'
>;
type ServiceFactory = (tx: CapacityWriteTransaction) => CapacityWriteOperations;
const service: ServiceFactory = (tx) => new CapacityWriteService(tx);

export function createCommitmentMutationDefinition(
  makeService: ServiceFactory = service,
): MutationDefinition<typeof createCommitmentSchema, CapacityCommitment> {
  return {
    schema: createCommitmentSchema,
    permission: { capability: 'commitment:write' as const, team: 'supply' as const },
    invalidMessage: 'Invalid capacity commitment.',
    async mutate({ input, tx, now }) {
      const result = await makeService(tx).createCommitment(input, now);
      return {
        data: result.commitment,
        activity: {
          type: 'note' as const,
          subject: `Created capacity commitment: ${result.commitment.name}`,
          accountId: result.commitment.accountId,
          supplyDealId: result.commitment.supplyDealId ?? undefined,
          meta: { capacityCommitmentId: result.commitment.id },
        },
      };
    },
  };
}

export function updateCommitmentMutationDefinition(
  makeService: ServiceFactory = service,
): MutationDefinition<typeof updateCommitmentSchema, CapacityCommitment> {
  return {
    schema: updateCommitmentSchema,
    permission: { capability: 'commitment:write' as const, team: 'supply' as const },
    invalidMessage: 'Invalid capacity commitment update.',
    async mutate({ input, params, tx, now }) {
      if (!params.id) throw MutationError.notFound('Capacity commitment');
      const result = await makeService(tx).updateCommitment(params.id, input, now);
      return {
        data: result.commitment,
        activity: {
          type: 'note' as const,
          subject: `Updated capacity commitment: ${result.commitment.name}`,
          accountId: result.commitment.accountId,
          supplyDealId: result.commitment.supplyDealId ?? undefined,
          meta: {
            capacityCommitmentId: result.commitment.id,
            changedFields: Object.keys(input),
            ...(input.terminatedAt !== undefined
              ? { terminatedAt: result.commitment.terminatedAt?.toISOString() ?? null }
              : {}),
          },
        },
      };
    },
  };
}

export function createAllocationMutationDefinition(
  makeService: ServiceFactory = service,
): MutationDefinition<typeof createAllocationSchema, Allocation> {
  return {
    schema: createAllocationSchema,
    permission: { capability: 'deal:write' as const, team: 'demand' as const },
    invalidMessage: 'Invalid allocation.',
    async mutate({ input, principal, tx, now }) {
      const result = await makeService(tx).createAllocation(input, principal, now);
      return {
        data: result.allocation,
        activity: {
          type: 'note' as const,
          subject: `Allocated ${input.gpuHours} GPU-hours from ${result.commitment.name}`,
          accountId: result.deal.accountId,
          demandDealId: result.deal.id,
          meta: {
            allocationId: result.allocation.id,
            capacityCommitmentId: result.commitment.id,
            status: result.allocation.status,
            pricePerGpuHourCents: result.allocation.pricePerGpuHourCents,
          },
        },
      };
    },
  };
}

export function createHoldMutationDefinition(
  makeService: ServiceFactory = service,
): MutationDefinition<typeof createHoldSchema, Allocation> {
  return {
    schema: createHoldSchema,
    permission: { capability: 'deal:write' as const, team: 'demand' as const },
    invalidMessage: 'Invalid capacity hold.',
    async mutate({ input, principal, tx, now }) {
      const result = await makeService(tx).createHold(input, principal, now);
      return {
        data: result.allocation,
        activity: {
          type: 'note' as const,
          subject: `Held ${input.gpuHours} GPU-hours from ${result.commitment.name}`,
          accountId: result.deal.accountId,
          demandDealId: result.deal.id,
          meta: {
            allocationId: result.allocation.id,
            capacityCommitmentId: result.commitment.id,
            holdExpiresAt: input.holdExpiresAt,
          },
        },
      };
    },
  };
}

export function releaseAllocationMutationDefinition(
  makeService: ServiceFactory = service,
): MutationDefinition<typeof releaseSchema, Allocation> {
  return {
    schema: releaseSchema,
    permission: { capability: 'deal:write' as const, team: 'demand' as const },
    invalidMessage: 'Invalid allocation release.',
    async mutate({ input, params, tx, now }) {
      if (!params.id) throw MutationError.notFound('Allocation');
      const result = await makeService(tx).releaseAllocation(params.id, input, now);
      return {
        data: result.allocation,
        activity: {
          type: 'note' as const,
          subject: `Released allocation ${result.allocation.id}`,
          accountId: result.accountId,
          demandDealId: result.allocation.demandDealId ?? undefined,
          meta: {
            allocationId: result.allocation.id,
            capacityCommitmentId: result.allocation.capacityCommitmentId,
            from: result.previousStatus,
            to: 'released',
            reason: input.reason,
          },
        },
      };
    },
  };
}

export function createCapacityWriteRoutes(db: Database): Hono<ApiEnv> {
  const routes = new Hono<ApiEnv>();
  routes.post('/api/commitments', mutation(db, createCommitmentMutationDefinition()));
  routes.patch('/api/commitments/:id', mutation(db, updateCommitmentMutationDefinition()));
  routes.post('/api/allocations', mutation(db, createAllocationMutationDefinition()));
  routes.post('/api/allocations/holds', mutation(db, createHoldMutationDefinition()));
  routes.post(
    '/api/allocations/:id/release',
    mutation(db, releaseAllocationMutationDefinition()),
  );
  return routes;
}
