<?php

namespace App\Services;

use App\Models\Client\Client;
use App\Models\Document\Document;
use App\Models\Document\DocumentType;
use Illuminate\Support\Facades\Storage;
use Spatie\LaravelPdf\Facades\Pdf;
use Spatie\Browsershot\Browsershot;

class ApplicationService {
    private PdfFormService $pdfFormService;
    private PdfService $pdfService;

    // Define your box dimensions here
    public const STATEMENT_MAX_LINES = 60;
    public const STATEMENT_MAX_CHARS_PER_LINE = 75;

    public function __construct() {
        $this->pdfFormService = new PdfFormService();
        $this->pdfService = new PdfService();
    }

    /**
     * Calculates if a string exceeds the physical box dimensions
     * based on max characters per line and max total lines.
     */
    private function statementExceedsBox(?string $statement): bool {
        if (empty($statement)) {
            return false;
        }

        // Normalize line endings
        $statement = str_replace("\r\n", "\n", $statement);

        // Split by existing hard returns
        $paragraphs = explode("\n", $statement);
        $totalLines = 0;

        foreach ($paragraphs as $paragraph) {
            if (trim($paragraph) === '') {
                $totalLines++; // Empty lines still take up a line of vertical space
                continue;
            }

            // Wrap the paragraph at our character limit.
            // The 4th param 'true' forces cuts for words longer than the limit.
            $wrapped = wordwrap($paragraph, self::STATEMENT_MAX_CHARS_PER_LINE, "\n", true);

            // Count the lines in this wrapped paragraph
            $totalLines += substr_count($wrapped, "\n") + 1;
        }

        return $totalLines > self::STATEMENT_MAX_LINES;
    }

    /**
     * Returns an array of files to be generated with their mapped fields and signature positions.
     * * @param Client $client
     * @return array
     */
    public function getPreviewData(Client $client): array {
        $this->validate($client);
        $solicitor = $client->solicitor;
        $claim = $client->claim;

        $preview = [];

        // Part 1 Data
        $preview['part_1'] = [
            'fields' => $this->getPart1Fields($client, $solicitor),
            'signature_positions' => [
                ['page' => 23, 'x' => 75, 'y' => 245, 'w' => 100, 'h' => 30]
            ]
        ];

        // Part 3 Data
        if ($claim->careSettings()->count() > 0) {
            $careSettings = $claim->careSettings;
            $firstFour = $careSettings->take(4);

            $preview['part_3'] = [
                'fields' => $this->getPart3Fields($client, $firstFour),
                'signature_positions' => [
                    ['page' => 13, 'x' => 75, 'y' => 245, 'w' => 100, 'h' => 30]
                ]
            ];

            // Part 3 Extra Pages
            if ($careSettings->count() > 4) {
                $extras = $careSettings->slice(4);
                foreach ($extras as $index => $setting) {
                    $key = "part_3_extra_" . ($index + 1);
                    $preview[$key] = [
                        'fields' => $this->getPart3ExtraFields($client, $setting),
                        'signature_positions' => []
                    ];
                }

                // Part 1 Extra (Summary of settings)
                $preview['part_1_extra'] = [
                    'fields' => $this->getPart1ExtraFields($client, $extras),
                    'signature_positions' => []
                ];
            }

            // Statement Attachment Previews (for any statement that exceeds the limit)
            foreach ($careSettings as $index => $setting) {
                if ($this->statementExceedsBox((string) $setting->statement)) {
                    $key = "part_3_statement_attachment_" . ($index + 1);
                    $preview[$key] = [
                        'fields' => $this->getStatementAttachmentFields($client, $setting),
                        'signature_positions' => []
                    ];
                }
            }
        }

        return $preview;
    }

    public function validate(Client $client) {
        if (!$client->currentAddress()->exists()) {
            throw new \Exception('Client address does not exist');
        }
        if (!$client->claim->impactDetail()->exists()) {
            throw new \Exception('Impact factors does not exist');
        }
    }

    private function getPart1Fields(Client $client, $solicitor): array {
        $claim = $client->claim;
        $ben = $claim->beneficiary ?: null;
        $ti = $claim->terminalIllness ?: null;
        $address = $client->currentAddress;
        $prefs = $client->preferences ?: [];

        $profTypes = [
            'gp' => 'GP',
            'consultant' => 'Consultant',
            'specialist_nurse' => 'Specialist',
            'district_nurse' => 'District'
        ];

        return [
            'client_last_name' => $client->name_last,
            'client_DOB_day' => $client->date_of_birth?->format('d'),
            'client_DOB_month' => $client->date_of_birth?->format('m'),
            'client_DOB_year' => $client->date_of_birth?->format('Y'),
            'client_address' => collect([$address->line_1, $address->line_2, $address->line_3])->filter()->implode("\n"),
            'client_address_country' => 'United Kingdom',
            'client_postcode' => $address->postcode,
            'client_first_name' => $client->name_first,
            'contact_letter_correspondence' => 'Yes',
            'contact_necessary_radio' => 'correspondence_addres',
            'contact_correspondence_addres' => "43-59 Princes Street,\nStockport",
            'contact_correspondence_country' => 'United Kingdom',
            'contact_correspondence_postcode' => 'SK1 1RY',
            'contact_format_braille' => $prefs['braille'] ? 'Yes' : 'No',
            'contact_format_large' => $prefs['large_print'] ? 'Yes' : 'No',
            'contact_format_audio' => $prefs['audio'] ? 'Yes' : 'No',
            'contact_format_other' => $prefs['other_lang'] ? 'Yes' : 'No',
            'contact_format_other_text' => $prefs['language_required'] ?? '',
            'contact_format_na' => $prefs['braille'] || $prefs['large_print'] || $prefs['audio'] || $prefs['other_lang'] ? 'No' : 'Yes',
            'help_complete' => 'No',
            'identity_radio' => 'Later',
            'help_first_name' => 'Matthew',
            'help_last_name' => 'Bell',
            'help_telephone' => '0161 532 3544',
            'help_email' => 'casemanagement@mmalegal.co.uk',
            'help_contact_type' => 'Solicitor',
            'help_contact_firm' => 'MMA Legal Limited',
            'ti_aps' => $ti?->applied_advance_payment ? 'Yes' : 'No',
            'ti_aps_confirm' => $ti?->permission_confirm_health ? 'Yes' : 'No',
            'ti_ds1500' => $ti?->provide_ds1500 ? 'Yes' : 'No',
            'ti_professional_check' => $ti?->agree_contact_healthcare ? 'Y' : 'No',
            'ti_nhs_number_check' => $ti?->has_medical_ref ? 'Yes' : 'No',
            'ti_nhs_number' => $ti?->medical_reference_number,
            'ti_prof_type' => $ti ? $profTypes[$ti?->healthcare_type] : '',
            'ti_prof_name' => $ti?->healthcare_name,
            'ti_prof_place' => $ti?->healthcare_place_of_work,
            'ti_prof_address' => $ti?->healthcare_address_1 . "\n" . $ti?->healthcare_address_2 . "\n" . $ti?->healthcare_address_3,
            'ti_prof_email' => (string)$ti?->healthcare_email,
            'ti_prof_tel' => $ti?->healthcare_phone,
            'ben_check' => $ben ? 'Yes' : 'No',
            'ben_name' => $ben?->full_name,
            'ben_DOB_day' => $ben?->date_of_birth?->format('d'),
            'ben_DOB_month' => $ben?->date_of_birth?->format('m'),
            'ben_DOB_year' => $ben?->date_of_birth?->format('Y'),
            'ben_address' => collect([$ben?->address_line_1, $ben?->address_line_2, $ben?->address_line_3, $ben?->address_line_4])->filter()->implode("\n"),
            'ben_tel' => $ben?->telephone,
            'ben_email' => (string)$ben?->email,
            'redress_type' => ucwords(strtolower($claim->redress_type)),
            'cs_1_name' => $claim->careSettings->get(0)?->institution->display_name,
            'cs_1_typ' => $claim->careSettings->get(0)?->institution->type->display_name,
            'cs_1_date' => $claim->careSettings->get(0)?->dates,
            'cs_2_name' => $claim->careSettings->get(1)?->institution->display_name,
            'cs_2_type' => $claim->careSettings->get(1)?->institution->type->display_name,
            'cs_2_date' => $claim->careSettings->get(1)?->dates,
            'cs_3_name' => $claim->careSettings->get(2)?->institution->display_name,
            'cs_3_type' => $claim->careSettings->get(2)?->institution->type->display_name,
            'cs_3_date' => $claim->careSettings->get(2)?->dates,
            'cs_4_name' => $claim->careSettings->get(3)?->institution->display_name,
            'cs_4_type' => $claim->careSettings->get(3)?->institution->type->display_name,
            'cs_4_date' => $claim->careSettings->get(3)?->dates,
            'aps' => $claim->furtherInformation?->apply_aps ? 'Yes' : 'No',
            'aps_confirm' => $claim->furtherInformation?->confirm_care_setting ? 'Yes' : 'No',
            'supporting_documents' => 'Later',
            'name_change_care_check' => is_null($claim->furtherInformation?->name_in_care) ? 'No' : 'Yes',
            'name_change_care' => $claim->furtherInformation?->name_in_care,
            'legal_name_change_check' => (is_null($claim->furtherInformation?->legal_name_change) && $client->previousNames()->count() == 0) ? 'No' : 'Yes',
            'legal_name_change' => $claim->furtherInformation?->legal_name ?: $client->previousNames()->first()?->fullName ?: '',
            'name_change_doc' => (is_null($claim->furtherInformation?->legal_name_change) && $client->previousNames()->count() == 0) ? 'No' : 'Yes',
            'payment' => $claim->impactDetail?->has_received_relevant_payment ? 'Yes' : 'No',
            'payment_date_day' => $claim->impactDetail?->payment_date?->format('d'),
            'payment_date_month' => $claim->impactDetail?->payment_date?->format('m'),
            'payment_date_year' => $claim->impactDetail?->payment_date?->format('Y'),
            'payment_organisation' => $claim->impactDetail?->paying_organisation,
            'payment_amount' => $claim->impactDetail?->payment_amount,
            'date' => date('d/m/Y'),
            'cc' => $claim->impactDetail?->has_criminal_conviction ? 'Yes' : 'No',
            'bank_name' => $claim->bankDetail?->name_on_account,
            'bank_sort_code' => $claim->bankDetail?->sort_code,
            'bank_sort_cod' => $claim->bankDetail?->sort_code,
            'bank_account_number' => $claim->bankDetail?->account_number,
            'bank_account_numb' => $claim->bankDetail?->account_number,
            'bank_reference' => $claim->bankDetail?->reference_number,
            'bank_bic' => $claim->bankDetail?->bic_swift,
            'bank_clearing_code' => $claim->bankDetail?->clearing_code,
            'bank_iban' => $claim->bankDetail?->iban,
        ];
    }

    private function getPart3Fields(Client $client, $careSettings): array {
        $data = [
            'client_full_name' => $client->name_first . " " . $client->name_last,
            'client_DOB_day' => $client->date_of_birth?->format('d'),
            'client_DOB_month' => $client->date_of_birth?->format('m'),
            'client_DOB_year' => $client->date_of_birth?->format('Y'),
            'supporting_documents' => 'collecting',
            'date_signed' => date("d/m/Y"),
        ];

        foreach ($careSettings->values() as $i => $setting) {
            $suffix = $i + 1;
            $data["setting_{$suffix}_name"] = $setting->institution->display_name;
            $data["setting_{$suffix}_date"] = $setting->dates;
            $data["setting_{$suffix}_persons_involved"] = $setting->people_involved;

            // Check Statement Length by simulating wordwrap
            if ($this->statementExceedsBox((string) $setting->statement)) {
                $data["setting_{$suffix}_statement"] = 'See Attached';
            } else {
                $data["setting_{$suffix}_statement"] = $setting->statement;
            }
        }

        return $data;
    }

    private function getPart3ExtraFields(Client $client, $setting): array {
        // Check Statement Length
        $statementOutput = $this->statementExceedsBox((string) $setting->statement)
            ? 'See Attached'
            : $setting->statement;

        return [
            'client_full_name' => $client->name_first . " " . $client->name_last,
            'client_DOB_day' => $client->date_of_birth?->format('d'),
            'client_DOB_month' => $client->date_of_birth?->format('m'),
            'client_DOB_year' => $client->date_of_birth?->format('Y'),
            'setting_name' => $setting->institution->display_name,
            'setting_date' => $setting->dates,
            'setting_persons_involved' => $setting->people_involved,
            'setting_persons_involve' => $setting->people_involved,
            'setting_statement' => $statementOutput,
        ];
    }

    private function getPart1ExtraFields(Client $client, $settings): array {
        $data = [
            'client_full_name' => $client->name_first . " " . $client->name_last,
            'client_DOB_day' => $client->date_of_birth?->format('d'),
            'client_DOB_month' => $client->date_of_birth?->format('m'),
            'client_DOB_year' => $client->date_of_birth?->format('Y'),
        ];

        foreach ($settings as $i => $setting) {
            $index = $i - 3;
            $data["additional_setting_{$index}_name"] = $setting->institution->display_name;
            $data["additional_setting_{$index}_type"] = $setting->institution->type->display_name;
            $data["additional_setting_{$index}_date"] = $setting->dates;
        }

        return $data;
    }

    /**
     * Extracts fields required specifically for the 'See Attached' statement addendum PDF.
     */
    private function getStatementAttachmentFields(Client $client, $setting): array {
        return [
            'client_full_name' => $client->name_first . " " . $client->name_last,
            'client_DOB_day' => $client->date_of_birth?->format('d'),
            'client_DOB_month' => $client->date_of_birth?->format('m'),
            'client_DOB_year' => $client->date_of_birth?->format('Y'),
            'setting_name' => $setting->institution->display_name,
            'setting_date' => $setting->dates,
            'setting_persons_involved' => $setting->people_involved,
            'full_statement' => $setting->statement,
        ];
    }

    /**
     * Dynamically generates a PDF containing the full, overflowed statement text.
     */
    private function generateStatementAttachmentPdf(Client $client, $setting, string $relativePath): void {
        $fields = $this->getStatementAttachmentFields($client, $setting);

        $dob = ($fields['client_DOB_day'] ?? '') . '/' . ($fields['client_DOB_month'] ?? '') . '/' . ($fields['client_DOB_year'] ?? '');

        // Basic HTML structure for the PDF
        $html = '
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                body { font-family: Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.6; color: #333; }
                .header { border-bottom: 2px solid #333; margin-bottom: 20px; padding-bottom: 10px; }
                .details { margin-bottom: 20px; width: 100%; }
                .details th { text-align: left; padding-right: 20px; vertical-align: top; width: 150px; }
                .statement { margin-top: 20px; background: #f9f9f9; padding: 15px; border: 1px solid #ddd; }
            </style>
        </head>
        <body>
            <div class="header">
                <h2>Statement Addendum</h2>
            </div>
            <table class="details">
                <tr><th>Client Name:</th><td>' . htmlspecialchars($fields['client_full_name'] ?? '') . '</td></tr>
                <tr><th>Date of Birth:</th><td>' . htmlspecialchars($dob) . '</td></tr>
                <tr><th>Care Setting:</th><td>' . htmlspecialchars($fields['setting_name'] ?? '') . '</td></tr>
                <tr><th>Dates:</th><td>' . htmlspecialchars($fields['setting_date'] ?? '') . '</td></tr>
                <tr><th>Persons Involved:</th><td>' . htmlspecialchars($fields['setting_persons_involved'] ?? '') . '</td></tr>
            </table>
            
            <div class="header" style="margin-top: 30px; border-bottom: 1px solid #ccc;">
                <h3>Full Statement</h3>
            </div>
            
            <div class="statement">' . nl2br(htmlspecialchars($fields['full_statement'] ?? '')) . '</div>
        </body>
        </html>';

        $pdf = Pdf::html($html)
            ->withBrowsershot(function (Browsershot $browsershot) {
                $chromeBase = storage_path('app/chrome');

                $browsershot
                    ->setChromePath('/usr/bin/chrome-puppeteer')
                    ->noSandbox()
                    ->setUserDataDir($chromeBase . '/data')
                    ->setEnvironmentOptions([
                        'HOME' => $chromeBase,
                        'XDG_CONFIG_HOME' => $chromeBase . '/config',
                        'XDG_CACHE_HOME' => $chromeBase . '/cache',
                        'CHROME_CONFIG_HOME' => $chromeBase . '/config',
                    ])
                    ->addChromiumArguments([
                        '--disable-dev-shm-usage',
                        '--disable-gpu',
                        '--headless=new',
                    ]);
            })->save(Storage::path($relativePath));
    }

    /**
     * Builds the application documents and optionally returns their Base64 representations.
     */
    public function build(Client $client, array $returnBase64Parts = []): array {
        $this->validate($client);

        $solicitor = $client->solicitor;
        $path = $client->getDocumentStoragePath();

        if (!Storage::disk('public')->exists($path)) {
            Storage::disk('public')->makeDirectory($path);
        }

        // Generate Part 1
        $this->pdfFormService->fill('Part1.pdf', $this->getPart1Fields($client, $solicitor), $path . "Part1.pdf");

        // Generate Part 3 & Extras
        if ($client->claim->careSettings()->count() > 0) {
            $careSettings = $client->claim->careSettings;
            $firstFour = $careSettings->take(4);

            $this->pdfFormService->fill('Part3.pdf', $this->getPart3Fields($client, $firstFour), $path . "Part3.pdf");

            // Handle standard part 3 extras
            if ($careSettings->count() > 4) {
                $extras = $careSettings->slice(4);
                foreach ($extras as $index => $setting) {
                    $this->pdfFormService->fill('Part3Extra.pdf', $this->getPart3ExtraFields($client, $setting), $path . "Part3Extra_{$index}.pdf");
                }

                $this->pdfFormService->fill('Part1Extra.pdf', $this->getPart1ExtraFields($client, $extras), $path . "Part1Extra.pdf");
            }

            // Generate "See Attached" Statement Extra PDFs natively
            foreach ($careSettings as $index => $setting) {
                $index = $index + 1;
                if ($this->statementExceedsBox((string) $setting->statement)) {
                    $attachmentPath = $path . "Part3StatementAttachment_{$index}.pdf";
                    $this->generateStatementAttachmentPdf($client, $setting, $attachmentPath);
                }
            }
        }

        $basePath = $client->getDocumentStoragePath();
        $timestamp = now()->format('Ymd_His');
        $generatedDocuments = [];

        // Merge logic for Part 1
        $part1Files = collect(["{$basePath}Part1.pdf", Storage::exists("{$basePath}Part1Extra.pdf") ? "{$basePath}Part1Extra.pdf" : null])->filter()->values()->toArray();
        if (!empty($part1Files)) {
            $mergedPath = "{$basePath}Application_Part1_{$timestamp}.pdf";
            $this->pdfService->merge($part1Files, $mergedPath);
            $generatedDocuments['Part 1'] = [
                'path' => $mergedPath,
                'base64' => in_array('Part 1', $returnBase64Parts) ? base64_encode(Storage::disk('public')->get($mergedPath)) : null,
            ];
        }

        // Merge logic for Part 3
        $part3Files = [];
        if (Storage::exists("{$basePath}Part3.pdf")) {
            $part3Files[] = "{$basePath}Part3.pdf";
        }

        if (isset($extras)) {
            foreach ($extras as $index => $setting) {
                if (Storage::exists("{$basePath}Part3Extra_{$index}.pdf")) {
                    $part3Files[] = "{$basePath}Part3Extra_{$index}.pdf";
                }
            }
        }

        // Append the new statement attachments to the very end of part 3
        if (isset($careSettings)) {
            foreach ($careSettings as $index => $setting) {
                if (Storage::exists("{$basePath}Part3StatementAttachment_{$index}.pdf")) {
                    $part3Files[] = "{$basePath}Part3StatementAttachment_{$index}.pdf";
                }
            }
        }

        if (!empty($part3Files)) {
            $mergedPath = "{$basePath}Application_Part3_{$timestamp}.pdf";
            $this->pdfService->merge($part3Files, $mergedPath);
            $generatedDocuments['Part 3'] = [
                'path' => $mergedPath,
                'base64' => in_array('Part 3', $returnBase64Parts) ? base64_encode(Storage::disk('public')->get($mergedPath)) : null,
            ];
        }

        return $generatedDocuments;
    }

    public function getDocumentBase64(string $path): ?string {
        if (Storage::disk('public')->exists($path)) {
            return base64_encode(Storage::disk('public')->get($path));
        }
        return null;
    }

    private function createDocumentRecord(Client $client, string $path, string $label, int $type = 0): Document {
        return $client->documents()->create([
            'display_name' => $label,
            'file_name' => basename($path),
            'file_type' => Storage::disk('public')->mimeType($path) ?? 'application/pdf',
            'file_size' => Storage::disk('public')->size($path),
            'type_id' => DocumentType::where('display_name', $label)->value('id'),
            'user_id' => auth()->id(),
        ]);
    }
}