<?php

declare(strict_types=1);

namespace Kylin\Parking\Services;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Kylin\Parking\Models\MonthlyCard;
use Kylin\Parking\Models\ParkingLot;
use Kylin\Parking\Models\ParkingRecord;
use Throwable;

/**
 * 停车服务
 *
 * 提供车辆入场、出场、计费、月卡管理、到期提醒等核心业务能力。
 *
 * 法律合规：
 *  - 《价格法》第十三条：明码标价，收费规则公开透明
 *  - 《价格法》第十四条：不得有不正当价格行为
 *  - 《个人信息保护法》第六条：最小必要原则
 *  - 《个人信息保护法》第四十二条：车牌号属于个人信息，加密存储
 *  - 《个人信息保护法》第四十七条：用户注销后应删除个人信息
 *  - 《网络安全法》第二十一条：操作日志留存 >= 180 天
 *  - GA/T 1028.3-2022：车牌识别置信度 >= 0.85
 *
 * 所有关键操作（入场/出场/月卡办理/续费）均写入审计日志，便于追溯。
 */
class ParkingService
{
    /**
     * 车辆入场
     *
     * 依据 GA/T 1028.3-2022，车牌识别置信度需 >= 0.85。
     *
     * @param array{
     *     lot_id: int,
     *     plate_no: string,
     *     confidence?: float
     * } $data
     * @return ParkingRecord
     *
     * @throws \RuntimeException 当停车场已满或车牌识别置信度不足
     */
    public function vehicleEntry(array $data): ParkingRecord
    {
        $lot = ParkingLot::lockForUpdate()->findOrFail($data['lot_id']);

        if ($lot->status !== 'active') {
            throw new \RuntimeException('停车场已停用');
        }

        if ($lot->available_spaces <= 0) {
            throw new \RuntimeException('车位已满');
        }

        // 车牌识别置信度校验（GA/T 1028.3-2022）
        $confidence = (float) ($data['confidence'] ?? 1.0);
        $threshold = (float) config('kylin-parking.plate_confidence_threshold', 0.85);
        if ($confidence < $threshold) {
            $this->audit('plate_recognition_low_confidence', ParkingLot::class, $lot->id, [
                'plate_no' => $data['plate_no'],
                'confidence' => $confidence,
                'threshold' => $threshold,
            ]);
            throw new \RuntimeException("车牌识别置信度 {$confidence} 低于阈值 {$threshold}（GA/T 1028.3-2022）");
        }

        // 检查是否已有未出场记录
        $existing = ParkingRecord::where('lot_id', $lot->id)
            ->where('plate_no', $data['plate_no'])
            ->where('status', 'parking')
            ->first();
        if ($existing) {
            throw new \RuntimeException('该车辆已在场内');
        }

        return DB::transaction(function () use ($lot, $data) {
            $lot->decrement('available_spaces');

            $record = ParkingRecord::create([
                'lot_id' => $lot->id,
                'plate_no' => $data['plate_no'],
                'entry_at' => now(),
                'status' => 'parking',
            ]);

            $this->audit('vehicle_entry', ParkingRecord::class, $record->id, [
                'lot_id' => $lot->id,
                'plate_no' => $data['plate_no'],
                'available_spaces' => $lot->available_spaces,
            ]);

            return $record;
        });
    }

    /**
     * 车辆出场并计费
     *
     * 依据《价格法》第十三条，计费规则公开透明；
     * 依据各地停车收费管理办法，免费时长 >= 15 分钟，日封顶收费。
     *
     * @return array{record: ParkingRecord, fee: int, free: bool}
     *
     * @throws \RuntimeException 当车辆无入场记录
     */
    public function vehicleExit(int $lotId, string $plateNo): array
    {
        return DB::transaction(function () use ($lotId, $plateNo) {
            $record = ParkingRecord::where('lot_id', $lotId)
                ->where('plate_no', $plateNo)
                ->where('status', 'parking')
                ->lockForUpdate()
                ->first();

            if (!$record) {
                throw new \RuntimeException('该车辆无入场记录');
            }

            $lot = ParkingLot::findOrFail($lotId);
            $exitAt = now();
            $durationMinutes = (int) $record->entry_at->diffInMinutes($exitAt);

            // 计算费用
            $fee = $this->calculateFee($durationMinutes);
            $free = $fee === 0;

            $record->update([
                'exit_at' => $exitAt,
                'duration_minutes' => $durationMinutes,
                'fee' => $fee,
                'status' => 'exited',
            ]);

            $lot->increment('available_spaces');

            $this->audit('vehicle_exit', ParkingRecord::class, $record->id, [
                'lot_id' => $lot->id,
                'plate_no' => $plateNo,
                'duration_minutes' => $durationMinutes,
                'fee' => $fee,
                'free' => $free,
            ]);

            return ['record' => $record, 'fee' => $fee, 'free' => $free];
        });
    }

    /**
     * 计算停车费用
     *
     * 计费规则：
     *  1. 免费时长内（默认 15 分钟）免费
     *  2. 首段时长（默认 60 分钟）收首段费用（默认 6 元）
     *  3. 后续按周期（默认 30 分钟）收周期费用（默认 3 元）
     *  4. 日封顶（默认 50 元）
     *
     * @param int $durationMinutes 停车时长（分钟）
     * @return int 费用（分）
     */
    public function calculateFee(int $durationMinutes): int
    {
        $freeMinutes = (int) config('kylin-parking.default_free_minutes', 15);
        $firstPeriodMinutes = (int) config('kylin-parking.default_first_period_minutes', 60);
        $firstPeriodFee = (int) config('kylin-parking.default_first_period_fee', 600);
        $periodMinutes = (int) config('kylin-parking.default_period_minutes', 30);
        $periodFee = (int) config('kylin-parking.default_period_fee', 300);
        $dailyCap = (int) config('kylin-parking.default_daily_cap', 5000);

        // 免费时长内
        if ($durationMinutes <= $freeMinutes) {
            return 0;
        }

        // 首段费用
        $fee = $firstPeriodFee;

        // 超出首段部分按周期计费
        if ($durationMinutes > $firstPeriodMinutes) {
            $extraMinutes = $durationMinutes - $firstPeriodMinutes;
            $periods = (int) ceil($extraMinutes / $periodMinutes);
            $fee += $periods * $periodFee;
        }

        // 日封顶
        return min($fee, $dailyCap);
    }

    /**
     * 办理月卡
     *
     * @param array{
     *     lot_id: int,
     *     plate_no: string,
     *     owner_name: string,
     *     owner_phone?: string,
     *     duration_months: int
     * } $data
     */
    public function createMonthlyCard(array $data): MonthlyCard
    {
        $startAt = now();
        $expiredAt = $startAt->copy()->addMonths((int) $data['duration_months']);

        $card = MonthlyCard::create([
            'lot_id' => (int) $data['lot_id'],
            'plate_no' => $data['plate_no'],
            'owner_name' => $data['owner_name'],
            'owner_phone' => $data['owner_phone'] ?? null,
            'start_at' => $startAt,
            'expired_at' => $expiredAt,
            'status' => 'active',
        ]);

        $this->audit('monthly_card_created', MonthlyCard::class, $card->id, [
            'lot_id' => $card->lot_id,
            'plate_no' => $data['plate_no'],
            'owner_name' => $data['owner_name'],
            'duration_months' => $data['duration_months'],
            'expired_at' => $expiredAt->toDateTimeString(),
        ]);

        return $card;
    }

    /**
     * 月卡续费
     */
    public function renewMonthlyCard(int $cardId, int $durationMonths): MonthlyCard
    {
        return DB::transaction(function () use ($cardId, $durationMonths) {
            $card = MonthlyCard::lockForUpdate()->findOrFail($cardId);

            // 若已过期，从当前时间开始计算；否则从到期时间延长
            $baseTime = $card->expired_at->isFuture() ? $card->expired_at : now();
            $newExpiredAt = $baseTime->copy()->addMonths($durationMonths);

            $card->update([
                'expired_at' => $newExpiredAt,
                'status' => 'active',
            ]);

            $this->audit('monthly_card_renewed', MonthlyCard::class, $card->id, [
                'lot_id' => $card->lot_id,
                'duration_months' => $durationMonths,
                'old_expired_at' => $card->expired_at->toDateTimeString(),
                'new_expired_at' => $newExpiredAt->toDateTimeString(),
            ]);

            return $card;
        });
    }

    /**
     * 获取即将到期的月卡（用于到期提醒）
     *
     * 依据各地停车收费管理办法，月卡到期前 7 天提醒续费。
     *
     * @return \Illuminate\Database\Eloquent\Collection<int, MonthlyCard>
     */
    public function getExpiringSoonCards()
    {
        return MonthlyCard::expiringSoon()->get();
    }

    /**
     * 用户注销时清理停车数据（PIPL 第 47 条）
     *
     * 依据《个人信息保护法》第四十七条，用户注销账号后，
     * 应当删除其个人信息（车牌号、车主姓名、手机号等）。
     *
     * @param string $plateNo 车牌号
     * @return array{records: int, cards: int} 清理数量
     */
    public function purgeByPlate(string $plateNo): array
    {
        $recordsCount = ParkingRecord::where('plate_no', $plateNo)->count();
        $cardsCount = MonthlyCard::where('plate_no', $plateNo)->count();

        ParkingRecord::where('plate_no', $plateNo)->delete();
        MonthlyCard::where('plate_no', $plateNo)->delete();

        $this->audit('parking_data_purged', ParkingRecord::class, 0, [
            'plate_no' => $plateNo,
            'records_deleted' => $recordsCount,
            'cards_deleted' => $cardsCount,
        ]);

        return ['records' => $recordsCount, 'cards' => $cardsCount];
    }

    /**
     * 记录审计日志
     *
     * 依据《网络安全法》第二十一条，操作日志保留 >= 180 天。
     *
     * @param string $event 事件名
     * @param string $modelType 模型类型
     * @param int $modelId 模型ID
     * @param array<string, mixed> $extra 额外数据
     */
    private function audit(string $event, string $modelType, int $modelId, array $extra = []): void
    {
        if (!class_exists(\Kylin\AuditLog\Models\AuditLog::class)) {
            return;
        }

        try {
            \Kylin\AuditLog\Models\AuditLog::create([
                'event' => $event,
                'model_type' => $modelType,
                'model_id' => $modelId,
                'new_values' => $extra,
                'old_values' => null,
                'user_id' => auth()->id(),
                'ip' => request()?->ip(),
                'url' => request()?->fullUrl(),
                'tag' => 'parking',
            ]);
        } catch (Throwable $e) {
            Log::warning('停车审计日志写入失败', [
                'error' => $e->getMessage(),
                'event' => $event,
                'model_type' => $modelType,
                'model_id' => $modelId,
            ]);
        }
    }
}
