<?php

namespace App\Services\AgentSettlement;

use App\Support\DatabaseUniqueViolation;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

/**
 * 已锁定账单的补差/冲正单（§21.1、§21.2）。
 */
final class AgentSettlementBillAdjustmentService
{
    public function __construct(
        private readonly AgentSettlementBillGuard $billGuard,
        private readonly AgentSettlementPeriodCompletionService $periodCompletion,
    ) {}

    public function createAdjustment(
        int $originalBillId,
        int $amount,
        string $adjustmentType,
        ?string $reason,
        int $adminUserId,
        ?string $idempotencyKey = null,
    ): int {
        $original = DB::table('settlement_bills')->where('id', $originalBillId)->first();
        if ($original === null) {
            throw new \InvalidArgumentException('bill_not_found');
        }

        if ($this->periodCompletion->isPeriodReadOnly((int) $original->settlement_period_id)) {
            throw ValidationException::withMessages([
                'period' => ['completed'],
            ]);
        }

        if (! in_array((string) $original->status, ['confirmed', 'partial_paid', 'settled', 'overdue'], true)) {
            throw ValidationException::withMessages([
                'bill' => ['not_locked'],
            ]);
        }

        if ($amount === 0) {
            throw ValidationException::withMessages([
                'amount' => ['zero'],
            ]);
        }

        if ($idempotencyKey !== null && $idempotencyKey !== '') {
            $existingBillId = $this->findAdjustmentBillByIdempotency($originalBillId, $idempotencyKey);
            if ($existingBillId !== null) {
                return $existingBillId;
            }
        }

        $type = in_array($adjustmentType, ['adjustment', 'reversal'], true)
            ? $adjustmentType
            : 'adjustment';

        try {
            return (int) DB::transaction(function () use ($original, $amount, $type, $reason, $adminUserId, $idempotencyKey): int {
                return $this->insertAdjustmentBill($original, $amount, $type, $reason, $adminUserId, $idempotencyKey);
            });
        } catch (QueryException $e) {
            if (
                $idempotencyKey !== null
                && $idempotencyKey !== ''
                && DatabaseUniqueViolation::matches($e)
            ) {
                $existingBillId = $this->findAdjustmentBillByIdempotency((int) $original->id, $idempotencyKey);
                if ($existingBillId !== null) {
                    return $existingBillId;
                }
            }

            throw $e;
        }
    }

    private function insertAdjustmentBill(
        object $original,
        int $amount,
        string $type,
        ?string $reason,
        int $adminUserId,
        ?string $idempotencyKey,
    ): int {
        if ($idempotencyKey !== null && $idempotencyKey !== '') {
            $existingBillId = $this->findAdjustmentBillByIdempotency((int) $original->id, $idempotencyKey);
            if ($existingBillId !== null) {
                return $existingBillId;
            }
        }

        $now = now();
        $newBillId = (int) DB::table('settlement_bills')->insertGetId([
            'settlement_period_id' => (int) $original->settlement_period_id,
            'bill_type' => $type,
            'owner_type' => (string) $original->owner_type,
            'owner_id' => (int) $original->owner_id,
            'counterparty_type' => (string) $original->counterparty_type,
            'counterparty_id' => (int) $original->counterparty_id,
            'gross_win_loss' => 0,
            'rebate_amount' => 0,
            'adjustment_amount' => $amount,
            'platform_rounding_adjustment' => 0,
            'net_amount' => $amount,
            'paid_amount' => 0,
            'unpaid_amount' => abs($amount),
            'status' => 'pending_confirm',
            'reversed_bill_id' => (int) $original->id,
            'meta_json' => json_encode([
                'original_bill_id' => (int) $original->id,
                'original_net_amount' => (int) $original->net_amount,
            ]),
            'created_at' => $now,
            'updated_at' => $now,
        ]);

        DB::table('settlement_adjustments')->insert([
            'settlement_period_id' => (int) $original->settlement_period_id,
            'original_bill_id' => (int) $original->id,
            'result_bill_id' => $newBillId,
            'adjustment_type' => $type,
            'amount' => $amount,
            'reason' => $reason,
            'idempotency_key' => $idempotencyKey,
            'created_by' => $adminUserId > 0 ? $adminUserId : null,
            'created_at' => $now,
            'updated_at' => $now,
        ]);

        return $newBillId;
    }

    private function findAdjustmentBillByIdempotency(int $originalBillId, string $idempotencyKey): ?int
    {
        $resultBillId = DB::table('settlement_adjustments')
            ->where('original_bill_id', $originalBillId)
            ->where('idempotency_key', $idempotencyKey)
            ->value('result_bill_id');

        return $resultBillId !== null ? (int) $resultBillId : null;
    }
}