<?php

namespace App\Services;

use App\Models\TrainingCertificate;
use App\Models\TrainingRegistration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;

/**
 * CertificationService
 *
 * Handles certificate generation, storage, and verification.
 * Supports both A4 Portrait and Landscape formats.
 *
 * Font resolution order (first found wins):
 *   1. storage/app/public/fonts/  (uploaded via admin)
 *   2. public/fonts/              (bundled with project)
 *   3. system fonts               (fallback — GD built-in)
 *
 * PDF generation uses barryvdh/laravel-dompdf if installed,
 * otherwise falls back gracefully to image-only certificates.
 *
 * @package App\Services
 */
class CertificationService
{
    /**
     * Certificate template paths (relative to public disk).
     */
    protected array $templates = [
        'portrait'  => 'certificates/template-portrait.png',
        'landscape' => 'certificates/template-landscape.png',
        'default'   => 'certificates/template-default.png',
    ];

    /**
     * Certificate dimensions in pixels (300 DPI A4).
     */
    protected array $dimensions = [
        'portrait'  => ['width' => 2480, 'height' => 3508],
        'landscape' => ['width' => 3508, 'height' => 2480],
    ];

    // =========================================================================
    // PUBLIC API
    // =========================================================================

    /**
     * Generate a certificate for a completed registration.
     */
    public function generateCertificate(TrainingRegistration $registration): ?TrainingCertificate
    {
        $package = $registration->trainingPackage;
        $user    = $registration->user;

        if (!$package->generates_certificate) {
            return null;
        }

        // Return existing certificate if already issued
        $existing = TrainingCertificate::where('user_id', $user->id)
            ->where('training_package_id', $package->id)
            ->first();

        if ($existing) {
            return $existing;
        }

        return DB::transaction(function () use ($registration, $package, $user) {
            $certificate = TrainingCertificate::create([
                'user_id'             => $user->id,
                'training_package_id' => $package->id,
                'registration_id'     => $registration->id,
                'title'               => $package->name,
                'description'         => "Successfully completed the {$package->name} training program.",
                'skills_acquired'     => $package->learning_objectives,
                'issue_date'          => now(),
                'format_type'         => $package->certificate_format,
                'trainer_name'        => $this->getLeadTrainerName($package),
                'trainer_title'       => 'Training Coordinator',
                'organization_name'   => config('app.organization_name', config('app.name')),
                'generated_by'        => auth()->id(),
            ]);

            // Generate image
            $imagePath = $this->generateCertificateImage($certificate);
            if ($imagePath) {
                $certificate->update(['certificate_path' => $imagePath]);
            }

            // Generate PDF (graceful — non-fatal if package not installed)
            $pdfPath = $this->generateCertificatePdf($certificate);
            if ($pdfPath) {
                $certificate->update(['pdf_path' => $pdfPath]);
            }

            // Mark registration as certificate issued
            $registration->markCertificateIssued($certificate->id);

            // Notify — wrapped so it never blocks certificate creation
            try {
                $notificationService = app(NotificationService::class);
                $notificationService->sendCertificateIssuedNotification($certificate);
            } catch (\Throwable $e) {
                Log::warning('Certificate notification failed (non-fatal)', [
                    'certificate_id' => $certificate->id,
                    'error'          => $e->getMessage(),
                ]);
            }

            Log::info('Certificate generated', [
                'certificate_id'     => $certificate->id,
                'certificate_number' => $certificate->certificate_number,
                'user_id'            => $user->id,
                'package_id'         => $package->id,
            ]);

            return $certificate;
        });
    }

    /**
     * Verify a certificate by verification code.
     */
    public function verifyCertificate(string $verificationCode): array
    {
        $certificate = TrainingCertificate::with(['user', 'trainingPackage'])
            ->where('verification_code', strtoupper(trim($verificationCode)))
            ->first();

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

        $certificate->recordVerification();

        return [
            'valid'       => $certificate->isValid(),
            'message'     => $certificate->isValid()
                ? 'This certificate is valid and authentic.'
                : 'This certificate has been ' . ($certificate->is_revoked ? 'revoked' : 'expired') . '.',
            'certificate' => [
                'number'          => $certificate->certificate_number,
                'holder_name'     => $certificate->user->name,
                'training_program'=> $certificate->trainingPackage->name,
                'issue_date'      => $certificate->getFormattedIssueDate(),
                'expiry_date'     => $certificate->getFormattedExpiryDate(),
                'status'          => $certificate->getStatusText(),
            ],
        ];
    }

    /**
     * Revoke a certificate.
     */
    public function revokeCertificate(TrainingCertificate $certificate, int $revokerId, string $reason): bool
    {
        return DB::transaction(function () use ($certificate, $revokerId, $reason) {
            $certificate->revoke($revokerId, $reason);

            Log::info('Certificate revoked', [
                'certificate_id' => $certificate->id,
                'revoker_id'     => $revokerId,
                'reason'         => $reason,
            ]);

            return true;
        });
    }

    /**
     * Restore a revoked certificate.
     */
    public function restoreCertificate(TrainingCertificate $certificate): bool
    {
        return DB::transaction(function () use ($certificate) {
            $certificate->restore();

            Log::info('Certificate restored', ['certificate_id' => $certificate->id]);

            return true;
        });
    }

    /**
     * Regenerate certificate image and PDF.
     */
    public function regenerateCertificate(TrainingCertificate $certificate): bool
    {
        try {
            // Delete old files
            if ($certificate->certificate_path) {
                Storage::disk('public')->delete($certificate->certificate_path);
            }
            if ($certificate->pdf_path) {
                Storage::disk('public')->delete($certificate->pdf_path);
            }

            $imagePath = $this->generateCertificateImage($certificate);
            if (!$imagePath) {
                return false;
            }

            $certificate->update(['certificate_path' => $imagePath]);

            $pdfPath = $this->generateCertificatePdf($certificate);
            if ($pdfPath) {
                $certificate->update(['pdf_path' => $pdfPath]);
            }

            return true;

        } catch (\Exception $e) {
            Log::error('Failed to regenerate certificate', [
                'certificate_id' => $certificate->id,
                'error'          => $e->getMessage(),
            ]);
            return false;
        }
    }

    /**
     * Download a certificate file.
     */
    public function downloadCertificate(TrainingCertificate $certificate, string $format = 'pdf')
    {
        if ($format === 'pdf' && $certificate->hasPdf()) {
            return Storage::disk('public')->download(
                $certificate->pdf_path,
                $certificate->certificate_number . '.pdf'
            );
        }

        if ($certificate->hasImage()) {
            return Storage::disk('public')->download(
                $certificate->certificate_path,
                $certificate->certificate_number . '.png'
            );
        }

        return null;
    }

    // =========================================================================
    // PROTECTED HELPERS
    // =========================================================================

    /**
     * Generate certificate image using Intervention Image.
     */
    protected function generateCertificateImage(TrainingCertificate $certificate): ?string
    {
        try {
            $manager    = new ImageManager(new Driver());
            $dimensions = $this->dimensions[$certificate->format_type] ?? $this->dimensions['portrait'];
            $template   = $this->getTemplatePath($certificate->format_type);

            if ($template && Storage::disk('public')->exists($template)) {
                $image = $manager->read(Storage::disk('public')->path($template));
            } else {
                // Build a clean blank certificate
                $image = $manager->create($dimensions['width'], $dimensions['height']);
                $image->fill('ffffff');
                // Outer border
                $image->drawRectangle(
                    40, 40,
                    $dimensions['width'] - 40,
                    $dimensions['height'] - 40,
                    function ($draw) { $draw->border(12, '#10b981'); }
                );
                // Inner border
                $image->drawRectangle(
                    70, 70,
                    $dimensions['width'] - 70,
                    $dimensions['height'] - 70,
                    function ($draw) { $draw->border(3, '#10b981'); }
                );
            }

            $this->addCertificateContent($image, $certificate, $dimensions);

            $filename = 'certificates/' . $certificate->certificate_number . '.png';
            Storage::disk('public')->put($filename, $image->toPng());

            return $filename;

        } catch (\Exception $e) {
            Log::error('Failed to generate certificate image', [
                'certificate_id' => $certificate->id,
                'error'          => $e->getMessage(),
            ]);
            return null;
        }
    }

    /**
     * Add text content to the certificate image.
     * Uses resolved font paths — falls back to GD built-in if TTF not found.
     */
    protected function addCertificateContent($image, TrainingCertificate $certificate, array $dimensions): void
    {
        $cx         = (int) ($dimensions['width'] / 2);
        $isPortrait = $certificate->format_type === 'portrait';

        $boldFont   = $this->resolveFontPath('arial-bold.ttf');
        $regularFont= $this->resolveFontPath('arial.ttf');

        // Helper closure to build font callback
        $font = function (string $fontFile, int $size, string $color, string $align = 'center') {
            return function ($f) use ($fontFile, $size, $color, $align) {
                if ($fontFile) {
                    $f->filename($fontFile);
                }
                $f->size($size);
                $f->color($color);
                $f->align($align);
                $f->valign('middle');
            };
        };

        // Title
        $image->text(
            'CERTIFICATE OF COMPLETION',
            $cx, $isPortrait ? 400 : 300,
            $font($boldFont, 120, '#10b981')
        );

        // Recipient name
        $image->text(
            $certificate->user->name,
            $cx, $isPortrait ? 700 : 550,
            $font($boldFont, 100, '#1f2937')
        );

        // Sub-heading
        $image->text(
            'Has successfully completed the training program:',
            $cx, $isPortrait ? 900 : 700,
            $font($regularFont, 50, '#6b7280')
        );

        // Training name
        $image->text(
            $certificate->trainingPackage->name,
            $cx, $isPortrait ? 1050 : 850,
            $font($boldFont, 80, '#1f2937')
        );

        // Issue date
        $image->text(
            'Issued on: ' . $certificate->issue_date->format('F j, Y'),
            $cx, $isPortrait ? 1300 : 1000,
            $font($regularFont, 40, '#6b7280')
        );

        // Certificate number
        $image->text(
            'Certificate No: ' . $certificate->certificate_number,
            $cx, $isPortrait ? 1400 : 1100,
            $font($regularFont, 35, '#9ca3af')
        );

        // Verification code
        $image->text(
            'Verification Code: ' . $certificate->verification_code,
            $cx, $isPortrait ? 1500 : 1180,
            $font($regularFont, 35, '#9ca3af')
        );

        // Organization name
        $image->text(
            $certificate->organization_name ?? config('app.name'),
            $cx, $isPortrait ? 2700 : 1800,
            $font($boldFont, 60, '#1f2937')
        );

        // Trainer name & title
        if ($certificate->trainer_name) {
            $trainerX = $cx - 400;
            $image->text(
                $certificate->trainer_name,
                $trainerX, $isPortrait ? 2300 : 1500,
                $font($boldFont, 50, '#1f2937')
            );
            $image->text(
                $certificate->trainer_title ?? 'Training Coordinator',
                $trainerX, $isPortrait ? 2380 : 1560,
                $font($regularFont, 35, '#6b7280')
            );
        }
    }

    /**
     * Generate PDF version of the certificate.
     *
     * Uses barryvdh/laravel-dompdf if installed.
     * Gracefully returns null if the package is not available.
     */
    protected function generateCertificatePdf(TrainingCertificate $certificate): ?string
    {
        // Check if dompdf/snappy is available without triggering a fatal error
        $hasDomPdf  = class_exists(\Barryvdh\DomPDF\Facade\Pdf::class);
        $hasSnappy  = class_exists(\Barryvdh\Snappy\Facades\SnappyPdf::class);

        if (!$hasDomPdf && !$hasSnappy) {
            Log::info('PDF generation skipped — no PDF package installed (dompdf/snappy)', [
                'certificate_id' => $certificate->id,
            ]);
            return null;
        }

        try {
            $viewData = ['certificate' => $certificate->load(['user', 'trainingPackage'])];

            if ($hasDomPdf) {
                $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView(
                    'training.certificates.pdf-template',
                    $viewData
                );
                $orientation = $certificate->format_type === 'landscape' ? 'landscape' : 'portrait';
                $pdf->setPaper('a4', $orientation);
            } else {
                $pdf = \Barryvdh\Snappy\Facades\SnappyPdf::loadView(
                    'training.certificates.pdf-template',
                    $viewData
                );
            }

            $filename = 'certificates/' . $certificate->certificate_number . '.pdf';
            Storage::disk('public')->put($filename, $pdf->output());

            return $filename;

        } catch (\Exception $e) {
            Log::error('Failed to generate certificate PDF', [
                'certificate_id' => $certificate->id,
                'error'          => $e->getMessage(),
            ]);
            return null;
        }
    }

    /**
     * Resolve a font file path.
     *
     * Search order:
     *   1. storage/app/public/fonts/{name}   (admin-uploaded)
     *   2. public/fonts/{name}               (project-bundled)
     *   3. null                              (GD built-in fallback)
     */
    protected function resolveFontPath(string $filename): ?string
    {
        // 1. Storage disk (admin-uploaded fonts)
        if (Storage::disk('public')->exists("fonts/{$filename}")) {
            return Storage::disk('public')->path("fonts/{$filename}");
        }

        // 2. Public directory (project-bundled fonts)
        $publicPath = public_path("fonts/{$filename}");
        if (file_exists($publicPath)) {
            return $publicPath;
        }

        // 3. No font found — Intervention Image will use GD built-in bitmap font
        Log::warning("Certificate font not found: {$filename}. Using GD built-in font.", [
            'searched' => [
                Storage::disk('public')->path("fonts/{$filename}"),
                $publicPath,
            ],
        ]);

        return null;
    }

    /**
     * Get template path for a given format type.
     */
    protected function getTemplatePath(string $formatType): ?string
    {
        return $this->templates[$formatType] ?? $this->templates['default'] ?? null;
    }

    /**
     * Get the lead trainer name for a package (from its sessions).
     */
    protected function getLeadTrainerName($package): ?string
    {
        $leadSession = $package->sessions()
            ->whereHas('trainers', fn ($q) => $q->wherePivot('role', 'lead'))
            ->with(['trainers' => fn ($q) => $q->wherePivot('role', 'lead')->limit(1)])
            ->first();

        return $leadSession?->trainers->first()?->name;
    }
}
