<?php

namespace App\Services\AgentSettlement;

use App\Models\Player;
use App\Services\Player\PlayerCreditService;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

/** 坏账核销：原账单保留，记 bad_debt 调整与归档单（§2、§21.1）。 */
final class AgentSettlementBadDebtService
{
    public function __construct(
        private readonly AgentSettlementPeriodCompletionService $periodCompletion,
        private readonly PlayerCreditService $playerCreditService,
    ) {}

    public function writeOff(int $originalBillId, ?string $reason, int $adminUserId): 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', 'overdue'], true)) {
            throw ValidationException::withMessages([
                'bill' => ['not_eligible'],
            ]);
        }

        $unpaid = (int) $original->unpaid_amount;
        if ($unpaid <= 0) {
            throw ValidationException::withMessages([
                'bill' => ['no_unpaid'],
            ]);
        }

        if (in_array((string) $original->bill_type, ['adjustment', 'reversal', 'bad_debt'], true)) {
            throw ValidationException::withMessages([
                'bill' => ['not_eligible'],
            ]);
        }

        return (int) DB::transaction(function () use ($original, $originalBillId, $unpaid, $reason, $adminUserId): int {
            $now = now();
            $periodId = (int) $original->settlement_period_id;

            $archiveBillId = (int) DB::table('settlement_bills')->insertGetId([
                'settlement_period_id' => $periodId,
                'bill_type' => 'bad_debt',
                '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' => -$unpaid,
                'platform_rounding_adjustment' => 0,
                'net_amount' => 0,
                'paid_amount' => 0,
                'unpaid_amount' => 0,
                'status' => 'settled',
                'reversed_bill_id' => $originalBillId,
                'meta_json' => json_encode([
                    'original_bill_id' => $originalBillId,
                    'written_off_amount' => $unpaid,
                    'original_net_amount' => (int) $original->net_amount,
                ]),
                'locked_at' => $now,
                'confirmed_at' => $now,
                'created_at' => $now,
                'updated_at' => $now,
            ]);

            DB::table('settlement_adjustments')->insert([
                'settlement_period_id' => $periodId,
                'original_bill_id' => $originalBillId,
                'adjustment_type' => 'bad_debt',
                'amount' => $unpaid,
                'reason' => $reason,
                'created_by' => $adminUserId > 0 ? $adminUserId : null,
                'created_at' => $now,
                'updated_at' => $now,
            ]);

            DB::table('settlement_bills')->where('id', $originalBillId)->update([
                'unpaid_amount' => 0,
                'status' => 'settled',
                'meta_json' => json_encode(array_merge(
                    $this->decodeMeta($original->meta_json),
                    [
                        'bad_debt_bill_id' => $archiveBillId,
                        'written_off_amount' => $unpaid,
                    ],
                )),
                'updated_at' => $now,
            ]);

            if ((string) $original->owner_type === 'player' && (int) $original->owner_id > 0 && (int) $original->net_amount > 0) {
                $player = Player::query()->find((int) $original->owner_id);
                if ($player !== null) {
                    $cumulativePaid = (int) $original->paid_amount + $unpaid;
                    $this->playerCreditService->releaseFromSettlement($player, $cumulativePaid, $originalBillId);
                }
            }

            $this->periodCompletion->syncIfReady($periodId);

            return $archiveBillId;
        });
    }

    /**
     * @return array<string, mixed>
     */
    private function decodeMeta(mixed $metaJson): array
    {
        if ($metaJson === null || $metaJson === '') {
            return [];
        }

        if (is_array($metaJson)) {
            return $metaJson;
        }

        $decoded = json_decode((string) $metaJson, true);

        return is_array($decoded) ? $decoded : [];
    }
}
