<?php

namespace App\Services\Reimbursements;

use App\Models\ReimbursementBatch;
use App\Models\ReimbursementBatchAdminFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use ZipArchive;

class ReimbursementEmailService
{
    public function __construct(private ReimbursementRecipientService $recipients)
    {
    }

    public function sendBatchEmail(
        int $batchId,
        string $recipientEmail,
        string $message,
        array $receiptIds,
        array $checkIds
    ): bool {
        $batch = ReimbursementBatch::query()->find($batchId);
        if (!$batch) {
            throw new \RuntimeException('Requested batch was not found.');
        }

        $receiptRows = !empty($receiptIds) ? $this->fetchBatchReceiptRows($batchId, $receiptIds) : [];

        $checkRows = [];
        if (!empty($checkIds)) {
            $checkRows = ReimbursementBatchAdminFile::query()
                ->select('id as file_id', 'admin_id', 'filename', 'original_filename', 'uploaded_at')
                ->where('batch_id', $batchId)
                ->whereIn('id', $checkIds)
                ->get()
                ->toArray();

            $adminLookup = [];
            foreach ($this->recipients->adminRoster() as $admin) {
                $adminLookup[(int) ($admin['id'] ?? 0)] = trim((string) (($admin['firstname'] ?? '') . ' ' . ($admin['lastname'] ?? '')));
            }
            foreach ($checkRows as &$row) {
                $aid = (int) ($row['admin_id'] ?? 0);
                $row['admin'] = $adminLookup[$aid] ?? ($aid > 0 ? 'Admin #' . $aid : 'Admin');
            }
            unset($row);
        }

        $tempDir = storage_path('tmp');
        if (!is_dir($tempDir)) {
            mkdir($tempDir, 0755, true);
        }

        $cleanupPaths = [];

        $batchLabel = trim((string) ($batch->title ?? '')) ?: ('Batch #' . ((int) ($batch->yearly_batch_number ?? $batchId)));
        $batchFileLabel = $this->sanitizeAttachmentComponent($batchLabel);

        $csvPath = tempnam($tempDir, 'batch_csv_');
        $cleanupPaths[] = $csvPath;
        $csvHandle = fopen($csvPath, 'w');
        if (!$csvHandle) {
            if (is_file($csvPath)) {
                @unlink($csvPath);
            }
            throw new \RuntimeException('Unable to create the summary file.');
        }

        $batchClosedDate = $this->formatCsvDate($batch->closed_at ?? null);
        $this->writeBatchCsvMetadata($csvHandle, $batchLabel, $batch->toArray());
        $this->writeBatchCsvSummary($csvHandle, $receiptRows, $batchClosedDate);
        fclose($csvHandle);

        $csvAttachmentName = $batchFileLabel . '_summary.csv';

        $receiptsArchivePath = null;
        $receiptZipCreated = false;
        if (!empty($receiptRows)) {
            $archivePath = $tempDir . '/batch_receipts_' . $batchId . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.zip';
            $zip = new ZipArchive();
            $added = 0;
            $usedNames = [];
            if ($zip->open($archivePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
                foreach ($receiptRows as $row) {
                    $sourcePath = storage_path('uploads/receipts/' . basename((string) ($row['receipt_filename'] ?? '')));
                    if (!is_file($sourcePath)) {
                        continue;
                    }
                    $firstName = $row['purchaser_firstname'] ?? '';
                    if (trim($firstName) === '' && !empty($row['purchaser_lastname'] ?? '')) {
                        $firstName = $row['purchaser_lastname'];
                    }
                    $firstName = $this->sanitizeAttachmentComponent($firstName);
                    $vendorPart = $this->sanitizeAttachmentComponent($row['retailor'] ?? '');
                    $datePart = $this->formatAttachmentDate($row['purchase_date'] ?? '');
                    $extension = pathinfo($sourcePath, PATHINFO_EXTENSION);
                    $extPart = $extension !== '' ? ('.' . strtolower($extension)) : '';
                    $baseName = $firstName . '_' . $vendorPart . '_' . $datePart;
                    $entryName = $this->uniqueZipEntryName($baseName, $extPart, $usedNames);
                    if ($zip->addFile($sourcePath, $entryName)) {
                        $added++;
                    }
                }
                $zip->close();
            }
            if ($added > 0 && is_file($archivePath)) {
                $receiptsArchivePath = $archivePath;
                $cleanupPaths[] = $archivePath;
                $receiptZipCreated = true;
            } elseif (is_file($archivePath)) {
                @unlink($archivePath);
            }
        }

        $checksArchivePath = null;
        $checksAdded = 0;
        if (!empty($checkRows)) {
            $archivePath = $tempDir . '/batch_checks_' . $batchId . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.zip';
            $zip = new ZipArchive();
            $usedNames = [];
            if ($zip->open($archivePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
                foreach ($checkRows as $row) {
                    $sourcePath = storage_path('uploads/reimbursements/' . basename((string) ($row['filename'] ?? '')));
                    if (!is_file($sourcePath)) {
                        continue;
                    }
                    $adminLabel = $row['admin'] ?? 'Admin';
                    $firstToken = strtok($adminLabel, ' ');
                    $namePart = $this->sanitizeAttachmentComponent($firstToken ?: $adminLabel);
                    $extension = pathinfo($sourcePath, PATHINFO_EXTENSION);
                    $extPart = $extension !== '' ? ('.' . strtolower($extension)) : '';
                    $entryName = $this->uniqueZipEntryName($namePart, $extPart, $usedNames);
                    if ($zip->addFile($sourcePath, $entryName)) {
                        $checksAdded++;
                    }
                }
                $zip->close();
            }
            if ($checksAdded > 0 && is_file($archivePath)) {
                $checksArchivePath = $archivePath;
                $cleanupPaths[] = $archivePath;
            } elseif (is_file($archivePath)) {
                @unlink($archivePath);
            }
        }

        $attachmentSummary = ['CSV Summary'];
        if ($receiptZipCreated) {
            $attachmentSummary[] = 'Receipt Files';
        }
        if ($checksArchivePath) {
            $attachmentSummary[] = 'Check Scans';
        }

        $subject = 'Reimbursement Batch Export: ' . $batchLabel;
        $bodyParts = [
            '<p>Please find attached the export for <strong>' . htmlspecialchars($batchLabel, ENT_QUOTES, 'UTF-8') . '</strong>.</p>',
        ];
        if (trim($message) !== '') {
            $bodyParts[] = '<p><strong>Message:</strong><br>' . nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8')) . '</p>';
        }
        $bodyParts[] = '<p>Attachments: ' . htmlspecialchars(implode(', ', $attachmentSummary), ENT_QUOTES, 'UTF-8') . '.</p>';
        $bodyParts[] = '<p>Regards,<br>Al Rahma Sunday School</p>';
        $htmlBody = implode("\n", $bodyParts);

        $attachments = [
            ['path' => $csvPath, 'name' => $csvAttachmentName],
        ];
        if ($receiptsArchivePath) {
            $attachments[] = ['path' => $receiptsArchivePath, 'name' => $batchFileLabel . '_receipts.zip'];
        }
        if ($checksArchivePath) {
            $attachments[] = ['path' => $checksArchivePath, 'name' => $batchFileLabel . '_checks.zip'];
        }

        $sender = $this->resolveSender('general');

        try {
            Mail::html($htmlBody, function ($message) use ($recipientEmail, $subject, $sender, $attachments) {
                $message->to($recipientEmail)->subject($subject);
                if (!empty($sender['email'])) {
                    $message->from($sender['email'], $sender['name']);
                }
                foreach ($attachments as $file) {
                    if (!empty($file['path']) && is_file($file['path'])) {
                        $message->attach($file['path'], ['as' => $file['name'] ?? basename($file['path'])]);
                    }
                }
            });

            return true;
        } catch (\Throwable $e) {
            Log::error('Failed to send batch export email: ' . $e->getMessage());
            return false;
        } finally {
            foreach ($cleanupPaths as $path) {
                if ($path && is_file($path)) {
                    @unlink($path);
                }
            }
        }
    }

    private function fetchBatchReceiptRows(int $batchId, array $receiptIds = []): array
    {
        $builder = DB::table('reimbursement_batch_items as bi')
            ->select('e.id as expense_id', 'e.retailor', 'e.amount', 'e.description', 'e.receipt_path as receipt_filename', 'e.date_of_purchase as purchase_date', 'u.firstname as purchaser_firstname', 'u.lastname as purchaser_lastname')
            ->join('expenses as e', 'e.id', '=', 'bi.expense_id')
            ->leftJoin('users as u', 'u.id', '=', 'e.purchased_by')
            ->where('bi.batch_id', $batchId);

        if (!empty($receiptIds)) {
            $builder->whereIn('e.id', $receiptIds);
        }

        $rows = $builder->get()->toArray();

        return array_values(array_filter(array_map(static function ($row) {
            return [
                'expense_id' => $row->expense_id ?? null,
                'retailor' => $row->retailor ?? null,
                'amount' => $row->amount ?? null,
                'description' => $row->description ?? null,
                'receipt_filename' => $row->receipt_filename ?? null,
                'purchase_date' => $row->purchase_date ?? null,
                'purchaser_firstname' => $row->purchaser_firstname ?? null,
                'purchaser_lastname' => $row->purchaser_lastname ?? null,
            ];
        }, $rows), static function ($row) {
            return !empty($row['receipt_filename']);
        }));
    }

    private function writeBatchCsvMetadata($handle, string $batchLabel, array $batch): void
    {
        $closedAtRaw = $batch['closed_at'] ?? null;
        fputcsv($handle, ['Batch Title', $batchLabel]);
        fputcsv($handle, ['School Year', $batch['school_year'] ?? '', 'Semester', $batch['semester'] ?? '', 'Closed At', $this->formatCsvDate($closedAtRaw)]);
        fputcsv($handle, []);
        fputcsv($handle, ['Amount', 'Credit', 'Paid', 'Balance', 'Date', 'By Who', 'Vendor', 'Description']);
    }

    private function writeBatchCsvSummary($handle, array $receiptRows, string $totalsDate = ''): void
    {
        $receiptTotalsByRecipient = [];
        foreach ($receiptRows as $row) {
            $recipientName = trim(($row['purchaser_firstname'] ?? '') . ' ' . ($row['purchaser_lastname'] ?? ''));
            if ($recipientName === '') {
                $recipientName = 'Unknown';
            }
            $vendor = trim((string) ($row['retailor'] ?? ''));
            if ($vendor === '') {
                $vendor = 'Vendor N/A';
            }
            $dateText = $this->formatCsvDate($row['purchase_date'] ?? null);
            $descriptionValue = trim((string) ($row['description'] ?? ''));
            $descriptionParts = $descriptionValue !== '' ? [$descriptionValue] : ['Receipt'];
            $amountValue = (float) ($row['amount'] ?? 0);
            $receiptTotalsByRecipient[$recipientName] = ($receiptTotalsByRecipient[$recipientName] ?? 0.0) + $amountValue;
            fputcsv($handle, [
                number_format($amountValue, 2, '.', ''),
                '',
                number_format($amountValue, 2, '.', ''),
                '0.00',
                $dateText,
                $recipientName,
                $vendor,
                implode(' | ', $descriptionParts),
            ]);
        }
        if (!empty($receiptTotalsByRecipient)) {
            fputcsv($handle, []);
            fputcsv($handle, ['Totals by Recipient', '', '', '', '', '', '', '']);
            foreach ($receiptTotalsByRecipient as $recipient => $total) {
                $formatted = number_format($total, 2, '.', '');
                fputcsv($handle, [
                    $formatted,
                    '',
                    $formatted,
                    '0.00',
                    $totalsDate,
                    $recipient,
                    '',
                    '',
                ]);
            }
        }
    }

    private function sanitizeAttachmentComponent(string $value): string
    {
        $clean = preg_replace('/[^A-Za-z0-9]+/', '_', trim((string) $value));
        $clean = trim($clean, '_');
        return $clean === '' ? 'unknown' : $clean;
    }

    private function formatAttachmentDate(?string $value): string
    {
        if (!$value) {
            return 'unknown';
        }
        $timestamp = strtotime($value);
        if ($timestamp === false) {
            return 'unknown';
        }
        return date('Ymd', $timestamp);
    }

    private function uniqueZipEntryName(string $base, string $extension, array &$used): string
    {
        $candidate = $base . $extension;
        $counter = 1;
        while (isset($used[$candidate])) {
            $candidate = $base . '_' . $counter . $extension;
            $counter++;
        }
        $used[$candidate] = true;
        return $candidate;
    }

    private function formatCsvDate(?string $value): string
    {
        if (!$value) {
            return '';
        }
        $timestamp = strtotime($value);
        if ($timestamp === false) {
            return '';
        }
        return date('m-d-Y', $timestamp);
    }

    private function resolveSender(string $fromKey): array
    {
        $fromKey = $fromKey !== '' ? $fromKey : 'general';
        $json = env('MAIL_SENDERS', '{}');
        $senders = json_decode($json, true);

        if (is_array($senders) && isset($senders[$fromKey])) {
            $info = $senders[$fromKey];
            return [
                'name' => (string) ($info['name'] ?? 'Sender'),
                'email' => (string) ($info['email'] ?? ''),
            ];
        }

        $fallbackAddress = (string) (config('mail.from.address') ?: (getenv('SMTP_USER') ?: ''));
        $fallbackName = (string) (config('mail.from.name') ?: 'Al Rahma Sunday School');

        return [
            'name' => $fallbackName,
            'email' => $fallbackAddress,
        ];
    }
}
