import { Injectable, NotFoundException } from '@nestjs/common';
import { DataSource } from 'typeorm';

import { CreateAllocationRuleDto, UpdateAllocationRuleDto } from './dto/allocation-rule.dto';
import { WarehouseAllocationRule } from './entities/warehouse-allocation-rule.entity';
import { WarehouseAllocationRuleRepository } from './warehouse-allocation-rule.repository';

export interface AllocationCriteria {
  freightType?: string | null; // CONTAINER | BULK
  tradeDirection?: string | null; // IMPORT | EXPORT | DOMESTIC | BOTH
  cargoTypeCode?: string | null;
  containerStatus?: string | null; // EMPTY | MAINTENANCE | ...
  requiresInspection?: boolean | null;
}

export interface AllocationResult {
  warehouseId: string;
  yardId: string;
  zoneId: string;
  facilityId: string | null;
  rule: { id: string; name: string; storageType: string | null } | null;
  /** Human-readable path: Facility → Warehouse → Yard → Zone. */
  path: string;
}

/**
 * Batch 5 — deterministic warehouse/yard allocation driven by configurable rules.
 * Never assigns randomly: matches criteria against active rules by priority and
 * resolves the target Yard/Warehouse/Zone by code.
 */
@Injectable()
export class WarehouseAllocationService {
  constructor(
    private readonly dataSource: DataSource,
    private readonly ruleRepository: WarehouseAllocationRuleRepository,
  ) {}

  // ── Rule CRUD ──────────────────────────────────────────────────────────────
  listRules(): Promise<WarehouseAllocationRule[]> {
    return this.ruleRepository.findAll({ order: { priority: 'ASC' } });
  }

  createRule(dto: CreateAllocationRuleDto): Promise<WarehouseAllocationRule> {
    return this.ruleRepository.create({ isActive: true, priority: 100, ...dto });
  }

  async updateRule(id: string, dto: UpdateAllocationRuleDto): Promise<WarehouseAllocationRule> {
    const updated = await this.ruleRepository.update(id, dto);
    if (!updated) throw new NotFoundException(`Allocation rule ${id} not found`);
    return updated;
  }

  deleteRule(id: string): Promise<void> {
    return this.ruleRepository.softDelete(id);
  }

  private matches(rule: WarehouseAllocationRule, c: AllocationCriteria): boolean {
    const eq = (ruleVal?: string | null, inVal?: string | null) =>
      ruleVal == null || (inVal != null && ruleVal.toUpperCase() === inVal.toUpperCase());
    return (
      eq(rule.freightType, c.freightType) &&
      eq(rule.tradeDirection, c.tradeDirection) &&
      eq(rule.cargoTypeCode, c.cargoTypeCode) &&
      eq(rule.containerStatus, c.containerStatus) &&
      (rule.requiresInspection == null || rule.requiresInspection === Boolean(c.requiresInspection))
    );
  }

  /** First active rule (by priority) whose criteria match. */
  async findMatchingRule(criteria: AllocationCriteria): Promise<WarehouseAllocationRule | null> {
    const rules = await this.ruleRepository.findAll({
      where: { isActive: true },
      order: { priority: 'ASC' },
    });
    return rules.find((r) => this.matches(r, criteria)) ?? null;
  }

  /** Resolve a concrete warehouse/yard/zone for the given criteria, or null if none configured. */
  async resolveLocation(criteria: AllocationCriteria): Promise<AllocationResult | null> {
    const rule = await this.findMatchingRule(criteria);
    const yardCode = rule?.targetYardCode;

    // Resolve yard (by rule code, else first available yard with a zone).
    const [yard] = await this.dataSource.query(
      yardCode
        ? `SELECT y.id, y.warehouse_id AS "warehouseId", y.name FROM freight.warehouse_yards y
             WHERE y.code = $1 AND y.deleted_at IS NULL LIMIT 1`
        : `SELECT y.id, y.warehouse_id AS "warehouseId", y.name FROM freight.warehouse_yards y
             JOIN freight.warehouse_zones z ON z.yard_id = y.id AND z.deleted_at IS NULL
             WHERE y.deleted_at IS NULL ORDER BY y.created_at ASC LIMIT 1`,
      yardCode ? [yardCode] : [],
    );
    if (!yard) return null;

    // Zone: rule code if given, else first zone in the yard.
    const [zone] = await this.dataSource.query(
      rule?.targetZoneCode
        ? `SELECT z.id, z.name FROM freight.warehouse_zones z WHERE z.code = $1 AND z.deleted_at IS NULL LIMIT 1`
        : `SELECT z.id, z.name FROM freight.warehouse_zones z WHERE z.yard_id = $1 AND z.deleted_at IS NULL ORDER BY z.created_at ASC LIMIT 1`,
      rule?.targetZoneCode ? [rule.targetZoneCode] : [yard.id],
    );
    if (!zone) return null;

    const [wh] = await this.dataSource.query(
      `SELECT w.id, w.name, w.facility_id AS "facilityId",
              (SELECT name FROM freight.facilities f WHERE f.id = w.facility_id) AS "facilityName"
         FROM freight.warehouses w WHERE w.id = $1 AND w.deleted_at IS NULL LIMIT 1`,
      [yard.warehouseId],
    );

    return {
      warehouseId: yard.warehouseId,
      yardId: yard.id,
      zoneId: zone.id,
      facilityId: wh?.facilityId ?? null,
      rule: rule ? { id: rule.id, name: rule.name, storageType: rule.storageType ?? null } : null,
      path: [wh?.facilityName, wh?.name, yard.name, zone.name].filter(Boolean).join(' → '),
    };
  }
}
