<?php

namespace App\Services;

use App\Models\Organization;
use App\Models\SelfPaced\Enrollment;
use App\Models\SelfPaced\ExamResult;
use App\Models\SelfPaced\SelfPacedCertificate;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

/**
 * SelfPacedCertificateService
 *
 * Handles certificate PDF generation (A4 Portrait & Landscape),
 * result slip PDF generation, and certificate verification.
 *
 * Property 6: Certificate issued only on pass.
 * Property 7: Certificate verification code uniqueness.
 */
class SelfPacedCertificateService
{
    /**
     * Storage disk for certificate PDFs.
     */
    protected string $disk = 'local';

    /**
     * Base storage path for certificate PDFs.
     */
    protected string $basePath = 'self-paced/certificates';

    /**
     * Base storage path for result slip PDFs.
     */
    protected string $slipPath = 'self-paced/result-slips';

    // =========================================================================
    // CERTIFICATE GENERATION
    // =========================================================================

    /**
     * Generate (or regenerate) a SelfPacedCertificate for a passed ExamResult.
     *
     * Property 6: Certificate SHALL be generated if and only if passed = true
     *             AND score_percentage >= passing_threshold.
     *
     * @throws \RuntimeException if the result is not a pass.
     */
    public function generate(ExamResult $result, int $generatedBy, string $format = 'portrait'): SelfPacedCertificate
    {
        // Property 6 guard
        if (!$result->passed || $result->score_percentage < $result->passing_threshold) {
            throw new \RuntimeException(
                'Certificate can only be issued for a passed exam result.'
            );
        }

        // Idempotent: return existing certificate if already issued for this result
        $existing = SelfPacedCertificate::where('result_id', $result->id)->first();
        if ($existing) {
            return $existing;
        }

        // Create the certificate record (model auto-generates number + code)
        $certificate = SelfPacedCertificate::create([
            'user_id'       => $result->user_id,
            'course_id'     => $result->course_id,
            'enrollment_id' => $result->enrollment_id,
            'result_id'     => $result->id,
            'issue_date'    => now()->toDateString(),
            'generated_by'  => $generatedBy,
        ]);

        // Generate and store the PDF
        try {
            $pdfPath = $this->renderCertificatePdf($certificate, $result, $format);
            $certificate->update(['pdf_path' => $pdfPath]);
        } catch (\Throwable $e) {
            Log::error('Certificate PDF generation failed', [
                'certificate_id' => $certificate->id,
                'error'          => $e->getMessage(),
            ]);
            // Certificate record is still valid even if PDF fails
        }

        Log::info('Self-paced certificate issued', [
            'certificate_id'     => $certificate->id,
            'certificate_number' => $certificate->certificate_number,
            'user_id'            => $result->user_id,
            'course_id'          => $result->course_id,
        ]);

        return $certificate;
    }

    /**
     * Regenerate the PDF for an existing valid certificate.
     *
     * @throws \RuntimeException if the certificate is revoked.
     */
    public function regeneratePdf(SelfPacedCertificate $certificate, string $format = 'portrait'): SelfPacedCertificate
    {
        if ($certificate->isRevoked()) {
            throw new \RuntimeException('Cannot regenerate PDF for a revoked certificate.');
        }

        $result = $certificate->result;

        // Delete old PDF if it exists
        if ($certificate->pdf_path && Storage::disk($this->disk)->exists($certificate->pdf_path)) {
            Storage::disk($this->disk)->delete($certificate->pdf_path);
        }

        $pdfPath = $this->renderCertificatePdf($certificate, $result, $format);
        $certificate->update(['pdf_path' => $pdfPath]);

        return $certificate->fresh();
    }

    /**
     * Stream a certificate PDF to the browser.
     * Returns the raw PDF content string.
     *
     * @throws \RuntimeException if the certificate is revoked or PDF is missing.
     */
    public function streamPdf(SelfPacedCertificate $certificate, string $format = 'portrait'): string
    {
        if ($certificate->isRevoked()) {
            throw new \RuntimeException('This certificate has been revoked.');
        }

        // Regenerate on-the-fly if PDF path is missing
        if (!$certificate->pdf_path || !Storage::disk($this->disk)->exists($certificate->pdf_path)) {
            $certificate = $this->regeneratePdf($certificate, $format);
        }

        return Storage::disk($this->disk)->get($certificate->pdf_path);
    }

    // =========================================================================
    // RESULT SLIP GENERATION
    // =========================================================================

    /**
     * Generate a result slip PDF for an ExamResult.
     * Stores the PDF and updates result_slip_path on the result.
     */
    public function generateResultSlip(ExamResult $result): string
    {
        $organization = Organization::first();
        $certificate  = $result->certificate ?? null;

        $pdf = Pdf::loadView('self-paced.pdf.result-slip', [
            'result'       => $result->load(['user', 'course', 'attempt']),
            'certificate'  => $certificate,
            'organization' => $organization,
        ]);

        $pdf->setPaper('A4', 'portrait');

        $filename = $this->slipPath . '/result-slip-' . $result->id . '-' . Str::random(8) . '.pdf';

        Storage::disk($this->disk)->put($filename, $pdf->output());

        $result->update(['result_slip_path' => $filename]);

        return $filename;
    }

    /**
     * Stream a result slip PDF to the browser.
     * Generates it first if not yet created.
     */
    public function streamResultSlip(ExamResult $result): string
    {
        if (!$result->result_slip_path || !Storage::disk($this->disk)->exists($result->result_slip_path)) {
            $this->generateResultSlip($result);
            $result->refresh();
        }

        return Storage::disk($this->disk)->get($result->result_slip_path);
    }

    // =========================================================================
    // VERIFICATION
    // =========================================================================

    /**
     * Verify a certificate by its verification code or certificate number.
     *
     * Property 7: Lookup returns exactly one certificate for a given code, or none.
     */
    public function verify(string $code): array
    {
        $code = strtoupper(trim($code));

        $certificate = SelfPacedCertificate::with(['user', 'course', 'enrollment'])
            ->where('verification_code', $code)
            ->orWhere('certificate_number', $code)
            ->first();

        if (!$certificate) {
            return [
                'found'   => false,
                'valid'   => false,
                'message' => 'Certificate not found. Please check the code and try again.',
            ];
        }

        // Record the verification hit
        $certificate->recordVerification();

        if ($certificate->isRevoked()) {
            return [
                'found'       => true,
                'valid'       => false,
                'message'     => 'Certificate Revoked',
                'certificate' => $this->formatCertificateData($certificate),
                'revoked_at'  => $certificate->revoked_at?->format('F j, Y'),
                'reason'      => $certificate->revocation_reason,
            ];
        }

        return [
            'found'       => true,
            'valid'       => true,
            'message'     => 'This certificate is valid and authentic.',
            'certificate' => $this->formatCertificateData($certificate),
        ];
    }

    /**
     * Format certificate data for the verification response.
     */
    protected function formatCertificateData(SelfPacedCertificate $certificate): array
    {
        return [
            'certificate_number' => $certificate->certificate_number,
            'verification_code'  => $certificate->verification_code,
            'holder_name'        => $certificate->user->name,
            'course_name'        => $certificate->course->title,
            'issue_date'         => $certificate->getFormattedIssueDate(),
            'status'             => $certificate->getStatusText(),
            'is_revoked'         => $certificate->is_revoked,
        ];
    }

    // =========================================================================
    // UNIQUE CODE GENERATION
    // =========================================================================

    /**
     * Generate a unique verification code, guaranteed not to collide with existing records.
     * Property 7: Certificate verification code uniqueness.
     */
    public function generateVerificationCode(): string
    {
        do {
            $code = strtoupper(Str::random(16));
        } while (SelfPacedCertificate::where('verification_code', $code)->exists());

        return $code;
    }

    /**
     * Generate a unique certificate number.
     */
    public function generateCertificateNumber(): string
    {
        $year = now()->format('Y');

        do {
            $random = strtoupper(Str::random(8));
            $number = "SP-CERT-{$year}-{$random}";
        } while (SelfPacedCertificate::where('certificate_number', $number)->exists());

        return $number;
    }

    // =========================================================================
    // INTERNAL PDF RENDERING
    // =========================================================================

    /**
     * Render the certificate PDF and store it, returning the storage path.
     *
     * @param string $format 'portrait' | 'landscape'
     */
    protected function renderCertificatePdf(
        SelfPacedCertificate $certificate,
        ExamResult $result,
        string $format = 'portrait'
    ): string {
        $format       = in_array($format, ['portrait', 'landscape'], true) ? $format : 'portrait';
        $organization = Organization::first();
        $verifyUrl    = url('/self-paced/verify?code=' . $certificate->verification_code);
        $qrDataUri    = $this->generateQrDataUri($verifyUrl);

        $view = "self-paced.pdf.certificate-{$format}";

        $pdf = Pdf::loadView($view, [
            'certificate'     => $certificate->load(['user', 'course', 'enrollment', 'result', 'generator']),
            'result'          => $result,
            'organization'    => $organization,
            'verificationUrl' => $verifyUrl,
            'qrDataUri'       => $qrDataUri,
        ]);

        if ($format === 'landscape') {
            $pdf->setPaper('A4', 'landscape');
        } else {
            $pdf->setPaper('A4', 'portrait');
        }

        $filename = $this->basePath
            . '/' . $certificate->certificate_number
            . '-' . $format
            . '.pdf';

        Storage::disk($this->disk)->put($filename, $pdf->output());

        return $filename;
    }

    /**
     * Generate a QR code as a base64 data URI using Google Charts API URL.
     * Falls back to a simple text placeholder if the URL is too long.
     *
     * We embed the URL as a data URI so DomPDF can render it without HTTP calls.
     */
    protected function generateQrDataUri(string $url): string
    {
        // Use a simple QR matrix approach via SVG for offline rendering
        // This generates a minimal QR-like placeholder that encodes the URL visually
        // For production, install a QR package; here we use a Google Charts-style URL
        // encoded as a data URI that DomPDF can render.

        $encodedUrl = urlencode($url);
        $apiUrl     = "https://api.qrserver.com/v1/create-qr-code/?size=150x150&data={$encodedUrl}";

        // Attempt to fetch the QR image and convert to base64 for offline embedding
        try {
            $context = stream_context_create([
                'http' => ['timeout' => 5, 'ignore_errors' => true],
                'ssl'  => ['verify_peer' => false, 'verify_peer_name' => false],
            ]);

            $imageData = @file_get_contents($apiUrl, false, $context);

            if ($imageData !== false && strlen($imageData) > 100) {
                return 'data:image/png;base64,' . base64_encode($imageData);
            }
        } catch (\Throwable $e) {
            // Silently fall through to SVG fallback
        }

        // SVG fallback — renders a simple bordered box with the verification URL
        $svg = $this->buildFallbackQrSvg($url);

        return 'data:image/svg+xml;base64,' . base64_encode($svg);
    }

    /**
     * Build a minimal SVG fallback when the QR API is unavailable.
     */
    protected function buildFallbackQrSvg(string $url): string
    {
        $short = strlen($url) > 40 ? substr($url, 0, 37) . '...' : $url;

        return <<<SVG
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150" viewBox="0 0 150 150">
  <rect width="150" height="150" fill="#ffffff" stroke="#0f3460" stroke-width="4"/>
  <rect x="10" y="10" width="40" height="40" fill="none" stroke="#0f3460" stroke-width="3"/>
  <rect x="18" y="18" width="24" height="24" fill="#0f3460"/>
  <rect x="100" y="10" width="40" height="40" fill="none" stroke="#0f3460" stroke-width="3"/>
  <rect x="108" y="18" width="24" height="24" fill="#0f3460"/>
  <rect x="10" y="100" width="40" height="40" fill="none" stroke="#0f3460" stroke-width="3"/>
  <rect x="18" y="108" width="24" height="24" fill="#0f3460"/>
  <text x="75" y="82" text-anchor="middle" font-family="monospace" font-size="7" fill="#374151">Scan QR</text>
  <text x="75" y="93" text-anchor="middle" font-family="monospace" font-size="5" fill="#6b7280">or visit URL</text>
</svg>
SVG;
    }
}
