<?php

namespace App\Services;

use App\Constants\HttpStatus;
use App\Models\BalanceLog;
use App\Models\Level;
use App\Models\ManualAudit as ManualAuditRecord;
use App\Models\User;
use App\Models\Wallet;
use Carbon\Carbon;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ManualAuditService
{
    public const DEBIT_CHANGE_TYPE = '人工扣款';
    public const STATUS_FAILED = ManualAuditRecord::STATUS_FAILED;
    public const STATUS_SUCCESS = ManualAuditRecord::STATUS_SUCCESS;

    public static function list(array $params): array
    {
        $page = max(1, (int)($params['page'] ?? 1));
        $limit = min(200, max(1, (int)($params['limit'] ?? 20)));
        $query = self::query($params);

        $total = (clone $query)->count('id');
        $rows = $query
            ->orderByDesc('id')
            ->forPage($page, $limit)
            ->get();

        return [
            'total' => $total,
            'data' => $rows->map(static fn(ManualAuditRecord $row): array => self::format($row))->all(),
            'options' => [
                'transaction_types' => [
                    ['label' => '人工上分', 'value' => 'credit'],
                    ['label' => '人工下分', 'value' => 'debit'],
                ],
                'credit_types' => array_map(static fn(string $type): array => [
                    'label' => $type,
                    'value' => $type,
                ], BalanceLogService::$manualRecharge),
                'statuses' => [
                    ['label' => '失败', 'value' => self::STATUS_FAILED],
                    ['label' => '成功', 'value' => self::STATUS_SUCCESS],
                ],
            ],
        ];
    }

    public static function detail(int $id): array
    {
        $row = self::query(['id' => $id])->first();
        if (!$row) {
            throw new Exception('人工稽查记录不存在', HttpStatus::CUSTOM_ERROR);
        }

        return self::format($row);
    }

    public static function credit(
        string $requestId,
        int $adminId,
        string $memberId,
        string $amount,
        string $changeType,
        string $remark
    ): array {
        return self::adjust($requestId, $adminId, $memberId, $amount, $changeType, $remark, false);
    }

    public static function debit(
        string $requestId,
        int $adminId,
        string $memberId,
        string $amount,
        string $remark
    ): array {
        return self::adjust(
            $requestId,
            $adminId,
            $memberId,
            $amount,
            self::DEBIT_CHANGE_TYPE,
            $remark,
            true
        );
    }

    private static function adjust(
        string $requestId,
        int $adminId,
        string $memberId,
        string $amount,
        string $changeType,
        string $remark,
        bool $isDebit
    ): array {
        $signedAmount = bcadd($amount, '0', 10);
        if ($isDebit) {
            $signedAmount = bcmul($signedAmount, '-1', 10);
        }
        $transactionType = $isDebit ? 'debit' : 'credit';
        $requestHash = self::requestHash(
            $adminId,
            $memberId,
            $signedAmount,
            $transactionType,
            $changeType,
            $remark
        );

        $existing = ManualAuditRecord::where('request_id', $requestId)->first();
        if ($existing) {
            self::assertRequestMatches($existing, $requestHash);
            return self::format($existing);
        }

        $created = false;
        try {
            $audit = DB::transaction(function () use (
                $requestId,
                $requestHash,
                $adminId,
                $memberId,
                $signedAmount,
                $transactionType,
                $changeType,
                $remark
            ): ManualAuditRecord {
                $audit = ManualAuditRecord::create([
                    'request_id' => $requestId,
                    'request_hash' => $requestHash,
                    'admin_id' => $adminId,
                    'member_id' => $memberId,
                    'username' => '',
                    'first_name' => '',
                    'transaction_type' => $transactionType,
                    'change_type' => $changeType,
                    'amount' => $signedAmount,
                    'level_before' => 0,
                    'level_before_name' => '普通会员',
                    'level_after' => 0,
                    'level_after_name' => '普通会员',
                    'status' => self::STATUS_FAILED,
                    'remark' => $remark,
                    'failure_reason' => '',
                ]);

                // Keep the same lock order as payment callbacks: wallet first, user second.
                $wallet = Wallet::where('member_id', $memberId)->lockForUpdate()->first();
                $user = User::where('member_id', $memberId)->lockForUpdate()->first();
                [$level, $levelName] = self::levelSnapshot($user);
                $audit->fill([
                    'username' => (string)($user->username ?? ''),
                    'first_name' => (string)($user->first_name ?? ''),
                    'level_before' => $level,
                    'level_before_name' => $levelName,
                    'level_after' => $level,
                    'level_after_name' => $levelName,
                ]);

                if (!$user) {
                    return self::fail($audit, '用户不存在');
                }
                if (!$wallet) {
                    return self::fail($audit, '用户钱包不存在');
                }

                $beforeBalance = bcadd((string)$wallet->available_balance, '0', 10);
                $afterBalance = bcadd($beforeBalance, $signedAmount, 10);
                $audit->before_balance = $beforeBalance;
                if (bccomp($afterBalance, '0', 10) < 0) {
                    $audit->after_balance = $beforeBalance;
                    return self::fail($audit, '可用余额不足');
                }

                $log = BalanceLogService::addLog(
                    $memberId,
                    $signedAmount,
                    $beforeBalance,
                    $afterBalance,
                    $changeType,
                    null,
                    $remark
                );
                $wallet->available_balance = $afterBalance;
                if (!$wallet->save()) {
                    throw new Exception('钱包更新失败', HttpStatus::CUSTOM_ERROR);
                }

                $audit->balance_log_id = $log->id;
                $audit->after_balance = $afterBalance;
                $audit->status = self::STATUS_SUCCESS;
                $audit->failure_reason = '';
                $audit->save();

                return $audit;
            }, 3);
            $created = true;
        } catch (QueryException $e) {
            $audit = ManualAuditRecord::where('request_id', $requestId)->first();
            if (!$audit) {
                throw $e;
            }
            self::assertRequestMatches($audit, $requestHash);
        }

        if ($created && (int)$audit->status === self::STATUS_SUCCESS) {
            self::notifyUser($memberId, (string)$audit->amount, (string)$audit->after_balance);
        }

        return self::format($audit);
    }

    private static function fail(ManualAuditRecord $audit, string $reason): ManualAuditRecord
    {
        $audit->status = self::STATUS_FAILED;
        $audit->failure_reason = $reason;
        $audit->save();

        return $audit;
    }

    private static function query(array $params = []): Builder
    {
        $query = ManualAuditRecord::query();

        if (!empty($params['id'])) {
            $query->where('id', (int)$params['id']);
        }
        if (!empty($params['member_id'])) {
            $query->where('member_id', (string)$params['member_id']);
        }
        if (!empty($params['first_name'])) {
            $query->where('first_name', 'like', '%' . $params['first_name'] . '%');
        }
        if (!empty($params['transaction_type'])) {
            $query->where('transaction_type', (string)$params['transaction_type']);
        }
        if (!empty($params['change_type'])) {
            $query->where('change_type', (string)$params['change_type']);
        }
        if (array_key_exists('status', $params) && $params['status'] !== null && $params['status'] !== '') {
            $query->where('status', (int)$params['status']);
        }
        if (!empty($params['start_date']) && !empty($params['end_date'])) {
            [$start, $end] = self::dateRange($params['start_date'], $params['end_date']);
            $query->whereBetween('created_at', [$start, $end]);
        }

        return $query;
    }

    private static function format(ManualAuditRecord $row): array
    {
        $amount = (string)$row->amount;
        $status = (int)$row->status;

        return [
            'id' => (int)$row->id,
            'request_id' => (string)$row->request_id,
            'admin_id' => $row->admin_id === null ? null : (int)$row->admin_id,
            'balance_log_id' => $row->balance_log_id === null ? null : (int)$row->balance_log_id,
            'member_id' => (string)$row->member_id,
            'username' => (string)($row->username ?? ''),
            'first_name' => (string)($row->first_name ?? ''),
            'transaction_type' => (string)$row->transaction_type,
            'transaction_type_text' => $row->transaction_type === 'debit' ? '人工下分' : '人工上分',
            'change_type' => (string)$row->change_type,
            'level_before' => (int)$row->level_before,
            'level_before_name' => (string)$row->level_before_name,
            'level_after' => (int)$row->level_after,
            'level_after_name' => (string)$row->level_after_name,
            'amount' => self::money(abs((float)$amount)),
            'signed_amount' => self::money((float)$amount),
            'before_balance' => self::nullableMoney($row->before_balance),
            'after_balance' => self::nullableMoney($row->after_balance),
            'status' => $status,
            'status_text' => $status === self::STATUS_SUCCESS ? '成功' : '失败',
            'remark' => (string)($row->remark ?? ''),
            'failure_reason' => (string)($row->failure_reason ?? ''),
            'created_at' => (string)$row->created_at,
        ];
    }

    /**
     * @return array{0:int,1:string}
     */
    private static function levelSnapshot(?User $user): array
    {
        if (!$user) {
            return [0, '普通会员'];
        }

        $level = (int)($user->level ?? 0);
        $levelName = (string)(Level::where('level', $level)->value('level_name') ?? '');
        if ($levelName === '') {
            $levelName = $level > 0 ? '等级' . $level : '普通会员';
        }

        return [$level, $levelName];
    }

    private static function requestHash(
        int $adminId,
        string $memberId,
        string $signedAmount,
        string $transactionType,
        string $changeType,
        string $remark
    ): string {
        $payload = json_encode([
            'admin_id' => $adminId,
            'member_id' => $memberId,
            'amount' => $signedAmount,
            'transaction_type' => $transactionType,
            'change_type' => $changeType,
            'remark' => $remark,
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);

        return hash('sha256', $payload);
    }

    private static function assertRequestMatches(ManualAuditRecord $audit, string $requestHash): void
    {
        if (!hash_equals((string)$audit->request_hash, $requestHash)) {
            throw new Exception('request_id 已被其他人工操作使用', HttpStatus::CUSTOM_ERROR);
        }
    }

    private static function notifyUser(string $memberId, string $amount, string $afterBalance): void
    {
        try {
            $formattedAmount = self::money((float)$amount);
            $formattedBalance = self::money((float)$afterBalance);
            TopUpService::notifyTransferSuccess(
                $memberId,
                '您的账户余额更新：' . ((float)$amount > 0 ? '+' : '') . $formattedAmount
                . " \n总余额为：" . $formattedBalance
            );
        } catch (\Throwable $e) {
            Log::warning('manual_audit_notify_failed', [
                'member_id' => $memberId,
                'error' => $e->getMessage(),
            ]);
        }
    }

    private static function money(float $amount): string
    {
        return number_format($amount, 2, '.', '');
    }

    private static function nullableMoney($amount): ?string
    {
        return $amount === null || $amount === '' ? null : self::money((float)$amount);
    }

    /**
     * @return array{0: Carbon, 1: Carbon}
     */
    private static function dateRange(string $startDate, string $endDate): array
    {
        $timezone = config('app.timezone', 'Asia/Shanghai');

        return [
            Carbon::createFromFormat('Y-m-d', $startDate, $timezone)->startOfDay(),
            Carbon::createFromFormat('Y-m-d', $endDate, $timezone)->endOfDay(),
        ];
    }
}
