<?php

namespace App\Services;

use App\Models\Company;
use App\Models\EmployeeCard;
use App\Models\User;
use Illuminate\Database\QueryException;
use Illuminate\Support\Str;

class QrCardService
{
    /**
     * Generate QR payload for employee card.
     */
    public function generateQrPayload(User $user): array
    {
        return [
            'type' => 'employee_card',
            'version' => 1,
            'employee_id' => $user->employee_id,
            'user_id' => $user->id,
            'company_id' => $user->company_id,
            'issued_at' => now()->timestamp,
        ];
    }

    /**
     * Sign payload using HMAC-SHA256.
     */
    public function signPayload(array $payload, string $secret): string
    {
        $payloadString = json_encode($payload, JSON_UNESCAPED_SLASHES);
        return hash_hmac('sha256', $payloadString, $secret);
    }

    /**
     * Verify QR signature.
     */
    public function verifySignature(array $payload, string $signature, string $secret): bool
    {
        $expectedSignature = $this->signPayload($payload, $secret);
        return hash_equals($expectedSignature, $signature);
    }

    /**
     * Create a new employee card for a user.
     * This will revoke any existing active cards.
     *
     * @throws \InvalidArgumentException When user has no company.
     */
    public function createCard(User $user): EmployeeCard
    {
        if (!$user->company_id || !$user->company) {
            throw new \InvalidArgumentException("User {$user->id} tidak memiliki perusahaan (company_id kosong).");
        }
        $company = $user->company;
        $secret = $this->ensureCompanySecret($company);

        $payload = $this->generateQrPayload($user);
        $signature = $this->signPayload($payload, $secret);

        // Revoke any existing active cards for this user
        EmployeeCard::where('user_id', $user->id)
            ->where('card_status', 'active')
            ->update([
                'card_status' => 'revoked',
                'revoked_at' => now(),
                'revoked_reason' => 'Kartu baru diterbitkan',
            ]);

        return $this->createCardRecord($user, $company, $payload, $signature);
    }

    /**
     * Insert one employee card row. Retries once on duplicate card_number (race condition).
     */
    private function createCardRecord(User $user, Company $company, array $payload, string $signature): EmployeeCard
    {
        try {
            return EmployeeCard::create([
                'user_id' => $user->id,
                'company_id' => $company->id,
                'card_number' => $this->generateCardNumber($company),
                'qr_payload' => $payload,
                'qr_signature' => $signature,
                'card_status' => 'active',
                'issued_at' => now(),
            ]);
        } catch (QueryException $e) {
            if ($e->getCode() === '23000' && str_contains($e->getMessage(), 'Duplicate entry') && str_contains($e->getMessage(), 'card_number')) {
                return EmployeeCard::create([
                    'user_id' => $user->id,
                    'company_id' => $company->id,
                    'card_number' => $this->generateCardNumber($company),
                    'qr_payload' => $payload,
                    'qr_signature' => $signature,
                    'card_status' => 'active',
                    'issued_at' => now(),
                ]);
            }
            throw $e;
        }
    }

    /**
     * Parse and validate QR code data from scanner.
     *
     * @return array{valid: bool, error?: string, user?: User, card?: EmployeeCard, company?: Company}
     */
    public function parseAndValidateQr(string $qrData): array
    {
        try {
            $decoded = json_decode($qrData, true);

            if (!$decoded || !isset($decoded['payload']) || !isset($decoded['signature'])) {
                return ['valid' => false, 'error' => 'Format QR tidak valid'];
            }

            $payload = $decoded['payload'];
            $signature = $decoded['signature'];

            // Check QR type
            if (!isset($payload['type']) || $payload['type'] !== 'employee_card') {
                return ['valid' => false, 'error' => 'Tipe QR tidak valid'];
            }

            // Check required fields
            if (!isset($payload['user_id']) || !isset($payload['company_id'])) {
                return ['valid' => false, 'error' => 'Data QR tidak lengkap'];
            }

            // Find company and verify signature
            $company = Company::find($payload['company_id']);
            if (!$company) {
                return ['valid' => false, 'error' => 'Perusahaan tidak ditemukan'];
            }

            if (!$company->qr_signing_secret) {
                return ['valid' => false, 'error' => 'Konfigurasi QR perusahaan tidak valid'];
            }

            if (!$this->verifySignature($payload, $signature, $company->qr_signing_secret)) {
                return ['valid' => false, 'error' => 'Tanda tangan QR tidak valid'];
            }

            // Find user
            $user = User::find($payload['user_id']);
            if (!$user) {
                return ['valid' => false, 'error' => 'Karyawan tidak ditemukan'];
            }

            if (!$user->is_active) {
                return ['valid' => false, 'error' => 'Akun karyawan tidak aktif'];
            }

            // Check card status
            $card = EmployeeCard::where('user_id', $user->id)
                ->where('card_status', 'active')
                ->first();

            if (!$card) {
                return ['valid' => false, 'error' => 'Kartu telah dicabut atau tidak ditemukan'];
            }

            if (!$card->isValid()) {
                return ['valid' => false, 'error' => 'Kartu sudah tidak berlaku'];
            }

            return [
                'valid' => true,
                'user' => $user,
                'card' => $card,
                'company' => $company,
            ];
        } catch (\Exception $e) {
            return ['valid' => false, 'error' => 'Gagal memproses QR: ' . $e->getMessage()];
        }
    }

    /**
     * Ensure company has a signing secret, generate one if not exists.
     */
    public function ensureCompanySecret(Company $company): string
    {
        if (!$company->qr_signing_secret) {
            $secret = Str::random(64);
            $company->update(['qr_signing_secret' => $secret]);
            return $secret;
        }

        return $company->qr_signing_secret;
    }

    /**
     * Generate unique card number.
     * Format: XXX26-00001 (prefix + year + sequence)
     * Uses max(card_number) for the year so we never reuse a sequence (avoids duplicate key).
     */
    public function generateCardNumber(Company $company): string
    {
        $prefix = strtoupper(substr($company->code ?? 'EMP', 0, 3));
        $year = date('y');
        $pattern = $prefix . $year . '-%';

        $lastCard = EmployeeCard::where('company_id', $company->id)
            ->where('card_number', 'like', $pattern)
            ->orderBy('card_number', 'desc')
            ->first();

        if ($lastCard) {
            $parts = explode('-', $lastCard->card_number);
            $sequence = isset($parts[1]) ? (int) $parts[1] + 1 : 1;
        } else {
            $sequence = 1;
        }

        return sprintf('%s%s-%05d', $prefix, $year, $sequence);
    }

    /**
     * Regenerate signing secret for a company.
     * Warning: This will invalidate all existing cards!
     */
    public function regenerateCompanySecret(Company $company): string
    {
        $secret = Str::random(64);
        $company->update(['qr_signing_secret' => $secret]);

        // Revoke all existing cards since signatures are now invalid
        EmployeeCard::where('company_id', $company->id)
            ->where('card_status', 'active')
            ->update([
                'card_status' => 'revoked',
                'revoked_at' => now(),
                'revoked_reason' => 'Signing secret direset',
            ]);

        return $secret;
    }

    /**
     * Batch create cards for multiple users.
     *
     * @param array<int|string> $userIds User IDs (array; single value is normalized to array).
     * @return array{created: int, failed: array}
     */
    public function batchCreateCards(array $userIds): array
    {
        $created = 0;
        $failed = [];
        $userIds = array_filter(array_values(is_array($userIds) ? $userIds : [$userIds]));

        foreach ($userIds as $userId) {
            try {
                $user = User::find($userId);
                if ($user && $user->is_active) {
                    $this->createCard($user);
                    $created++;
                } else {
                    $failed[] = [
                        'user_id' => $userId,
                        'reason' => $user ? 'User tidak aktif' : 'User tidak ditemukan',
                    ];
                }
            } catch (\Exception $e) {
                $failed[] = [
                    'user_id' => $userId,
                    'reason' => $e->getMessage(),
                ];
            }
        }

        return [
            'created' => $created,
            'failed' => $failed,
        ];
    }
}
