<?php

namespace App\Http\Controllers\Company\Reports;

use App\Exports\GeneralLedgerExport;
use App\Http\Controllers\Controller;
use App\Models\Setting;
use App\Services\LedgerReportService;
use Barryvdh\DomPDF\Facade\Pdf;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Maatwebsite\Excel\Excel as ExcelFormat;
use Maatwebsite\Excel\Facades\Excel;

class GeneralLedgerController extends Controller
{
    public function __construct(private readonly LedgerReportService $reports)
    {
    }

    public function index(Request $request)
    {
        $this->gate();

        $payload = $this->reportPayload($request);

        return view('reports.general-ledger.index', $payload);
    }

    public function export(Request $request)
    {
        $this->gate();

        $payload = $this->reportPayload($request);

        if (!$payload['ledger']) {
            return back()->with('error', 'Select an account and date range to export the General Ledger.');
        }

        $company = auth()->user()->company;
        $periodLabel = $this->periodLabel($payload['dateFrom'], $payload['dateTo']);
        $filename = 'general-ledger-' . now()->format('Y-m-d-His') . '.xlsx';

        return Excel::download(
            new GeneralLedgerExport(
                $payload['ledger'],
                (int) auth()->user()->company_id,
                $company?->name ?? config('app.name'),
                $periodLabel,
                $payload['dateFrom'],
            ),
            $filename,
        );
    }

    public function exportPdf(Request $request)
    {
        $this->gate();

        $payload = $this->reportPayload($request);

        if (!$payload['ledger']) {
            return back()->with('error', 'Select an account and date range to download the General Ledger PDF.');
        }

        $company = auth()->user()->company;
        $companyId = (int) auth()->user()->company_id;
        $periodLabel = $this->periodLabel($payload['dateFrom'], $payload['dateTo']);
        $accountLabel = $this->accountLabel($payload);
        $companyName = $company?->name ?? config('app.name');

        $pdf = Pdf::loadView(
            'reports.general-ledger.pdf',
            $this->pdfViewData($payload, $companyId, $companyName, $periodLabel, $accountLabel),
        );
        $pdf->setPaper('a4', 'landscape');

        return $pdf->download('general-ledger-' . now()->format('Y-m-d-His') . '.pdf');
    }

    public function email(Request $request)
    {
        $this->gate();

        $validated = $request->validate([
            'recipient' => 'required|email|max:150',
            'subject' => 'nullable|string|max:180',
            'message' => 'nullable|string|max:2000',
            'format' => 'required|in:pdf,xlsx',
            'paper_size' => 'nullable|in:a4,letter,legal',
            'layout' => 'nullable|in:portrait,landscape',
        ]);

        $payload = $this->reportPayload($request);

        if (!$payload['ledger']) {
            return back()->with('error', 'Select an account and date range to email the General Ledger.');
        }

        $companyId = (int) auth()->user()->company_id;
        $company = auth()->user()->company;
        $periodLabel = $this->periodLabel($payload['dateFrom'], $payload['dateTo']);
        $companyName = $company?->name ?? config('app.name');
        $accountLabel = $this->accountLabel($payload);
        $subject = trim((string) ($validated['subject'] ?? ''))
            ?: "General Ledger — {$companyName} ({$accountLabel}, {$periodLabel})";
        $message = trim((string) ($validated['message'] ?? ''))
            ?: "Please find the attached General Ledger report for {$companyName}.\n\nAccount: {$accountLabel}\nPeriod: {$periodLabel}";
        $attachment = $this->generalLedgerAttachment(
            $payload,
            $companyId,
            $companyName,
            $periodLabel,
            $validated['format'],
            $validated['format'] === 'pdf' ? ($validated['paper_size'] ?? 'a4') : null,
            $validated['format'] === 'pdf' ? ($validated['layout'] ?? 'portrait') : null,
        );

        try {
            $this->configureCompanyMail($companyId);

            Mail::raw($message, function ($mail) use ($validated, $subject, $attachment, $companyName, $companyId) {
                $fromEmail = Setting::get($companyId, 'smtp_from_email');
                $fromName = Setting::get($companyId, 'smtp_from_name', $companyName);

                $mail->to($validated['recipient'])
                    ->from($fromEmail, $fromName)
                    ->subject($subject)
                    ->attachData($attachment['content'], $attachment['filename'], ['mime' => $attachment['mime']]);
            });

            $formatLabel = $validated['format'] === 'pdf' ? 'PDF' : 'Excel';

            return back()->with('success', "General Ledger report ({$formatLabel}) emailed to {$validated['recipient']}.");
        } catch (Exception $e) {
            return back()
                ->withInput()
                ->with('error', 'Failed to email report: ' . $e->getMessage());
        }
    }

    private function reportPayload(Request $request): array
    {
        $companyId = auth()->user()->company_id;
        $defaults = $this->reports->defaultDateRange($companyId);
        $dateFrom = $request->input('date_from', $defaults['date_from']);
        $dateTo = $request->input('date_to', $defaults['date_to']);
        $filterOptions = $this->reports->generalLedgerFilterOptions($companyId);
        $filters = $this->normalizeGeneralLedgerFilters($request, $filterOptions);
        $selection = $this->resolveGeneralLedgerAccountSelection($request);

        $ledger = null;
        if ($selection['all_ledger']) {
            $ledger = $this->reports->generalLedger($companyId, 'all', $dateFrom, $dateTo, $filters);
        } elseif (count($selection['account_ids']) === 1) {
            $ledger = $this->reports->generalLedger($companyId, $selection['account_ids'][0], $dateFrom, $dateTo, $filters);
        } elseif (count($selection['account_ids']) > 1) {
            $ledger = $this->reports->generalLedgerForAccounts($companyId, $selection['account_ids'], $dateFrom, $dateTo, $filters);
        }

        return [
            'allLedger' => $selection['all_ledger'],
            'selectedAccountIds' => $selection['account_ids'],
            'accountId' => $selection['all_ledger'] ? 'all' : ($selection['account_ids'][0] ?? null),
            'dateFrom' => $dateFrom,
            'dateTo' => $dateTo,
            'ledger' => $ledger,
            'filterOptions' => $filterOptions,
            'filters' => $filters,
            'selectedAccountsLabel' => $this->selectedAccountsLabel($companyId, $selection),
            'selectedAccountTypesLabel' => $this->multiFilterLabel(
                $filters['account_sections'],
                $filterOptions['accountTypes'],
                'All account types',
            ),
            'selectedVoucherTypesLabel' => $this->multiFilterLabel(
                $filters['voucher_types'],
                $filterOptions['voucherTypes'],
                'All voucher types',
            ),
            'selectedDimensionsLabel' => $this->dimensionFilterLabel(
                $filters['dimension_value_ids'],
                $filterOptions['dimensionTree'] ?? [],
            ),
            'exportQuery' => $request->query(),
        ];
    }

    private function resolveGeneralLedgerAccountSelection(Request $request): array
    {
        if ($request->boolean('all_ledger') || $request->input('account_id') === 'all') {
            return ['all_ledger' => true, 'account_ids' => []];
        }

        $accountIds = array_values(array_unique(array_filter(array_map(
            'intval',
            (array) $request->input('account_ids', []),
        ))));

        if ($accountIds === [] && $request->filled('account_id')) {
            $legacyId = $request->integer('account_id');
            if ($legacyId > 0) {
                $accountIds = [$legacyId];
            }
        }

        return ['all_ledger' => false, 'account_ids' => $accountIds];
    }

    private function selectedAccountsLabel(int $companyId, array $selection): string
    {
        if ($selection['all_ledger']) {
            return 'All Ledger';
        }

        $accountIds = $selection['account_ids'];
        if ($accountIds === []) {
            return 'Select account(s)';
        }

        $accounts = \App\Models\Account::withoutGlobalScopes()
            ->where('company_id', $companyId)
            ->whereIn('id', $accountIds)
            ->get(['id', 'code', 'name']);

        if ($accounts->count() === 1) {
            $account = $accounts->first();

            return $account->code
                ? '[' . $account->code . '] ' . $account->name
                : $account->name;
        }

        return $accounts->count() . ' accounts selected';
    }

    private function normalizeGeneralLedgerFilters(Request $request, array $filterOptions): array
    {
        return [
            'account_sections' => $this->stringArrayFilter(
                $request->input('account_sections', $request->input('account_types')),
                array_keys($filterOptions['accountTypes'] ?? []),
            ),
            'voucher_types' => $this->stringArrayFilter(
                $request->input('voucher_types'),
                array_keys($filterOptions['voucherTypes'] ?? []),
            ),
            'dimension_value_ids' => array_values(array_unique(array_filter(array_map(
                'intval',
                (array) $request->input('dimension_value_ids', []),
            )))),
            'search' => trim((string) $request->input('search', '')),
        ];
    }

    private function stringArrayFilter(mixed $input, array $allowed): array
    {
        $values = is_array($input) ? $input : (filled($input) ? [$input] : []);

        return array_values(array_intersect(
            array_unique(array_filter(array_map('strval', $values))),
            $allowed,
        ));
    }

    private function multiFilterLabel(array $selected, array $options, string $emptyLabel): string
    {
        if ($selected === []) {
            return $emptyLabel;
        }

        if (count($selected) === 1) {
            $key = $selected[0];

            return $options[$key] ?? $key;
        }

        return count($selected) . ' selected';
    }

    private function dimensionFilterLabel(array $selectedIds, array $dimensionTree): string
    {
        if ($selectedIds === []) {
            return 'All dimensions';
        }

        $selectedLookup = array_fill_keys(array_map('intval', $selectedIds), true);
        $names = [];

        $walk = function (array $nodes) use (&$walk, &$names, $selectedLookup) {
            foreach ($nodes as $node) {
                if (!empty($node['selectable']) && isset($selectedLookup[(int) $node['id']])) {
                    $names[] = $node['name'];
                }

                if (!empty($node['children'])) {
                    $walk($node['children']);
                }
            }
        };

        $walk($dimensionTree);

        if (count($names) === 1) {
            return $names[0];
        }

        if ($names !== []) {
            return count($names) . ' dimensions selected';
        }

        return count($selectedIds) . ' dimensions selected';
    }

    private function periodLabel(string $dateFrom, string $dateTo): string
    {
        return \Carbon\Carbon::parse($dateFrom)->format('d M Y')
            . ' to '
            . \Carbon\Carbon::parse($dateTo)->format('d M Y');
    }

    private function accountLabel(array $payload): string
    {
        return $payload['selectedAccountsLabel'] ?? '—';
    }

    private function generalLedgerAttachment(
        array $payload,
        int $companyId,
        string $companyName,
        string $periodLabel,
        string $format,
        ?string $paperSize = null,
        ?string $layout = null,
    ): array {
        $dateStamp = now()->format('Y-m-d');
        $accountLabel = $this->accountLabel($payload);

        if ($format === 'pdf') {
            $pdf = Pdf::loadView('reports.general-ledger.pdf', $this->pdfViewData($payload, $companyId, $companyName, $periodLabel, $accountLabel));
            $pdf->setPaper($paperSize ?? 'a4', $layout ?? 'landscape');

            return [
                'content' => $pdf->output(),
                'filename' => "general-ledger-{$dateStamp}.pdf",
                'mime' => 'application/pdf',
            ];
        }

        return [
            'content' => Excel::raw(
                new GeneralLedgerExport(
                    $payload['ledger'],
                    $companyId,
                    $companyName,
                    $periodLabel,
                    $payload['dateFrom'],
                ),
                ExcelFormat::XLSX,
            ),
            'filename' => "general-ledger-{$dateStamp}.xlsx",
            'mime' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        ];
    }

    private function pdfViewData(array $payload, int $companyId, string $companyName, string $periodLabel, string $accountLabel): array
    {
        $money = function ($amount) use ($companyId) {
            if (abs((float) $amount) < 0.005) {
                return '—';
            }

            return \App\Helpers\NumberHelper::formatAmount((float) abs($amount), $companyId);
        };

        return [
            'ledger' => $payload['ledger'],
            'companyName' => $companyName,
            'periodLabel' => $periodLabel,
            'accountLabel' => $accountLabel,
            'dateFrom' => $payload['dateFrom'],
            'money' => $money,
        ];
    }

    private function configureCompanyMail(int $companyId): void
    {
        $host = trim((string) Setting::get($companyId, 'smtp_host', ''));
        $fromEmail = trim((string) Setting::get($companyId, 'smtp_from_email', ''));
        $fromName = trim((string) Setting::get($companyId, 'smtp_from_name', ''));

        if ($host === '' || $fromEmail === '' || $fromName === '') {
            throw new \RuntimeException('SMTP is not configured. Please set up outgoing email in Company Settings.');
        }

        config([
            'mail.default' => 'smtp',
            'mail.mailers.smtp.host' => $host,
            'mail.mailers.smtp.port' => (int) Setting::get($companyId, 'smtp_port', 587),
            'mail.mailers.smtp.username' => Setting::get($companyId, 'smtp_username', ''),
            'mail.mailers.smtp.password' => Setting::get($companyId, 'smtp_password', ''),
            'mail.mailers.smtp.encryption' => Setting::get($companyId, 'smtp_encryption', 'tls') === 'none'
                ? null
                : Setting::get($companyId, 'smtp_encryption', 'tls'),
            'mail.mailers.smtp.timeout' => 15,
            'mail.from.address' => $fromEmail,
            'mail.from.name' => $fromName,
        ]);
    }

    private function gate(): void
    {
        if (!auth()->user()->hasPermission('reports', 'view')) {
            abort(403);
        }
    }
}
