<?php

namespace App\Modules\Procurement\Services;

use App\Modules\Procurement\Models\GoodsReceipt;
use App\Modules\Procurement\Models\GoodsReceiptItem;
use App\Modules\Procurement\Models\PurchaseOrderItem;
use App\Modules\Procurement\Support\GoodsReceiptViolationCode;

final class GoodsReceiptIntegrityAuditService
{
    public function __construct(
        private readonly GoodsReceiptObservability $obs,
    ) {}

    /**
     * @return array{
     *   severity:string,
     *   active_cross_po:int,
     *   active_deleted_poi:int,
     *   active_missing_poi:int,
     *   quarantined_lines:int,
     *   affected_receipt_ids: list<int>,
     *   affected_line_ids: list<int>
     * }
     */
    public function auditTenant(int $tenantId): array
    {
        $activeCross = 0;
        $activeDeleted = 0;
        $activeMissing = 0;
        $receiptIds = [];
        $lineIds = [];

        $rows = GoodsReceiptItem::withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->orderBy('id')
            ->cursor();

        foreach ($rows as $line) {
            $receipt = GoodsReceipt::withoutGlobalScopes()->find((int) $line->goods_receipt_id);
            $code = $this->classifyLine($line, $receipt);
            if ($code === null) {
                continue;
            }
            if ($line->quarantined_at === null) {
                $lineIds[] = (int) $line->id;
                $receiptIds[(int) $line->goods_receipt_id] = true;
                match ($code) {
                    GoodsReceiptViolationCode::CROSS_PO => $activeCross++,
                    GoodsReceiptViolationCode::DELETED_PO_ITEM => $activeDeleted++,
                    GoodsReceiptViolationCode::MISSING_PO_ITEM => $activeMissing++,
                    default => null,
                };
                $this->logActiveCorruption($code, $line, $receipt);
            }
        }

        $quarantined = (int) GoodsReceiptItem::withoutGlobalScopes()
            ->where('tenant_id', $tenantId)
            ->whereNotNull('quarantined_at')
            ->count();

        $severity = $this->severity($activeCross, $activeDeleted, $activeMissing, $quarantined);

        return [
            'severity' => $severity,
            'active_cross_po' => $activeCross,
            'active_deleted_poi' => $activeDeleted,
            'active_missing_poi' => $activeMissing,
            'quarantined_lines' => $quarantined,
            'affected_receipt_ids' => array_keys($receiptIds),
            'affected_line_ids' => $lineIds,
        ];
    }

    private function classifyLine(GoodsReceiptItem $line, ?GoodsReceipt $receipt): ?string
    {
        if ($receipt === null) {
            return GoodsReceiptViolationCode::MISSING_PO_ITEM;
        }
        $poi = PurchaseOrderItem::withoutGlobalScopes()->withTrashed()->find((int) $line->purchase_order_item_id);
        if ($poi === null) {
            return GoodsReceiptViolationCode::MISSING_PO_ITEM;
        }
        if ($poi->trashed()) {
            return GoodsReceiptViolationCode::DELETED_PO_ITEM;
        }
        if ((int) $poi->purchase_order_id !== (int) $receipt->purchase_order_id) {
            return GoodsReceiptViolationCode::CROSS_PO;
        }

        return null;
    }

    /**
     * @param  array<string, mixed>  $context
     */
    private function logActiveCorruption(string $code, GoodsReceiptItem $line, ?GoodsReceipt $receipt): void
    {
        $ctx = [
            'tenant_id' => (int) $line->tenant_id,
            'goods_receipt_item_id' => $line->id,
            'goods_receipt_id' => $line->goods_receipt_id,
            'code' => $code,
        ];
        if ($code === GoodsReceiptViolationCode::CROSS_PO) {
            $this->obs->crossPoReceiptContamination($ctx);
        } elseif ($code === GoodsReceiptViolationCode::MISSING_PO_ITEM) {
            $this->obs->orphanDetected($ctx);
        } else {
            $this->obs->invalidReceiptLine($code, $ctx);
        }
    }

    private function severity(int $cross, int $deleted, int $missing, int $quarantined): string
    {
        if ($cross > 0) {
            return 'critical';
        }
        if ($deleted > 0 || $missing > 0) {
            return 'high';
        }
        if ($quarantined > 0) {
            return 'contained';
        }

        return 'clean';
    }
}

