<?php

namespace App\Services;

use App\Models\InventoryMovement;
use App\Models\Item;
use App\Models\Receiving;
use App\Models\ReceivingItem;
use App\Models\SyncInbox;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Throwable;

class CloudSyncService
{
    public function __construct(private InventoryService $inventoryService) {}

    public function process(array $event): array
    {
        $inbox = SyncInbox::where('event_uuid', $event['event_uuid'])->first();

        if ($inbox && $inbox->status === 'processed') {
            return [
                'duplicate' => true,
                'event_uuid' => $event['event_uuid'],
                'status' => 'processed',
            ];
        }

        $inbox = SyncInbox::updateOrCreate(
            ['event_uuid' => $event['event_uuid']],
            [
                'origin_server_code' => $event['origin_server_code'],
                'farm_id' => $event['farm_id'] ?? null,
                'aggregate_type' => $event['aggregate_type'],
                'aggregate_uuid' => $event['aggregate_uuid'],
                'action' => $event['action'],
                'data_hash' => $event['data_hash'] ?? null,
                'payload' => $event['payload'],
                'status' => 'pending',
                'last_error' => null,
            ]
        );

        try {
            $result = DB::transaction(function () use ($event, $inbox) {
                $result = match ($event['aggregate_type']) {
                    'receiving' => $this->processReceiving($event),
                    default => throw new \InvalidArgumentException("Unsupported aggregate type: {$event['aggregate_type']}"),
                };

                $inbox->update([
                    'status' => 'processed',
                    'processed_at' => now(),
                    'last_error' => null,
                ]);

                return $result;
            });

            return [
                'duplicate' => false,
                'event_uuid' => $event['event_uuid'],
                'status' => 'processed',
                'result' => $result,
            ];
        } catch (Throwable $exception) {
            $inbox->update([
                'status' => 'failed',
                'last_error' => mb_substr($exception->getMessage(), 0, 5000),
            ]);

            throw $exception;
        }
    }

    private function processReceiving(array $event): array
    {
        $payload = $event['payload'];
        $existing = Receiving::with('items')->where('sync_uuid', $payload['sync_uuid'])->first();

        $receiving = Receiving::updateOrCreate(
            ['sync_uuid' => $payload['sync_uuid']],
            [
                'origin_server_code' => $payload['origin_server_code'] ?? $event['origin_server_code'],
                'synced_status' => 'synced',
                'synced_at' => now(),
                'last_sync_error' => null,
                'data_hash' => $event['data_hash'] ?? null,
                'document_no' => $payload['document_no'],
                'farm_id' => $payload['farm_id'],
                'receiving_date' => $payload['receiving_date'],
                'supplier' => $payload['supplier'] ?? null,
                'type' => $payload['type'],
                'mode' => $payload['mode'],
                'external_reference' => $payload['external_reference'] ?? null,
                'remarks' => $payload['remarks'] ?? null,
                'user_id' => $payload['user_id'],
            ]
        );

        $incomingItemUuids = [];

        foreach ($payload['items'] as $row) {
            $item = Item::findOrFail($row['item_id']);
            $incomingItemUuids[] = $row['sync_uuid'];

            ReceivingItem::updateOrCreate(
                ['sync_uuid' => $row['sync_uuid']],
                [
                    'origin_server_code' => $row['origin_server_code'] ?? $event['origin_server_code'],
                    'synced_status' => 'synced',
                    'synced_at' => now(),
                    'last_sync_error' => null,
                    'data_hash' => $event['data_hash'] ?? null,
                    'receiving_id' => $receiving->id,
                    'item_id' => $item->id,
                    'item_name_snapshot' => $row['item_name'] ?? $item->name,
                    'quantity' => $row['quantity'],
                    'unit_cost' => $row['unit_cost'] ?? 0,
                ]
            );

            $this->syncReceivingMovement($receiving, $row, $event);
        }

        $removedItems = $receiving->items()
            ->whereNotIn('sync_uuid', $incomingItemUuids)
            ->get();

        foreach ($removedItems as $removedItem) {
            InventoryMovement::where('reference_type', 'receiving')
                ->where('reference_id', $receiving->id)
                ->where('item_id', $removedItem->item_id)
                ->where('farm_id', $receiving->farm_id)
                ->where('type', 'IN')
                ->delete();

            $this->inventoryService->syncItemSOH($removedItem->item_id);
            $removedItem->delete();
        }

        return [
            'action' => $existing ? 'updated' : 'created',
            'receiving_id' => $receiving->id,
            'sync_uuid' => $receiving->sync_uuid,
        ];
    }

    private function syncReceivingMovement(Receiving $receiving, array $row, array $event): void
    {
        $movement = InventoryMovement::where('reference_type', 'receiving')
            ->where('reference_id', $receiving->id)
            ->where('item_id', $row['item_id'])
            ->where('farm_id', $receiving->farm_id)
            ->where('type', 'IN')
            ->first();

        if ($movement) {
            $sohExcluding = $this->inventoryService->computeSOHExcluding($row['item_id'], $receiving->farm_id, $movement->id);
            $newBalance = $sohExcluding + $row['quantity'];

            $movement->update([
                'origin_server_code' => $event['origin_server_code'],
                'synced_status' => 'synced',
                'synced_at' => now(),
                'last_sync_error' => null,
                'data_hash' => $event['data_hash'] ?? null,
                'quantity' => $row['quantity'],
                'unit_cost' => $row['unit_cost'] ?? 0,
                'total_cost' => $row['quantity'] * ($row['unit_cost'] ?? 0),
                'balance_after' => $newBalance,
                'remarks' => $receiving->remarks,
                'user_id' => $receiving->user_id,
            ]);
        } else {
            $newBalance = $this->inventoryService->computeSOH($row['item_id'], $receiving->farm_id) + $row['quantity'];

            InventoryMovement::create([
                'sync_uuid' => (string) Str::uuid(),
                'origin_server_code' => $event['origin_server_code'],
                'synced_status' => 'synced',
                'synced_at' => now(),
                'last_sync_error' => null,
                'data_hash' => $event['data_hash'] ?? null,
                'farm_id' => $receiving->farm_id,
                'item_id' => $row['item_id'],
                'type' => 'IN',
                'quantity' => $row['quantity'],
                'unit_cost' => $row['unit_cost'] ?? 0,
                'total_cost' => $row['quantity'] * ($row['unit_cost'] ?? 0),
                'balance_after' => $newBalance,
                'reference_type' => 'receiving',
                'reference_id' => $receiving->id,
                'remarks' => $receiving->remarks,
                'user_id' => $receiving->user_id,
            ]);
        }

        $this->inventoryService->syncItemSOH($row['item_id']);
    }
}
