<?php

declare(strict_types=1);

namespace App\Modules\Reporting\Presentation\Controllers;

use App\Core\Auth;
use App\Core\Request;
use App\Core\Response;
use App\Modules\Reporting\Application\Services\PartyStatementService;
use App\Services\PrintSettingsService;

final class PartyStatementController
{
    public function __construct(private ?PartyStatementService $service = null)
    {
        $this->service ??= new PartyStatementService(db());
    }

    public function show(string $role = 'customer'): void
    {
        Auth::requireLogin();
        $role = $this->normalizeRole($role);
        require_permission($role === 'supplier' ? 'suppliers.statement.view' : 'sales.statement.view');
        $this->service->ensureSchema();

        $partyId = (int) Request::get('party_id', Request::get($role === 'supplier' ? 'supplier_id' : 'customer_id', 0));
        $from = trim((string) Request::get('from', ''));
        $to = trim((string) Request::get('to', ''));
        $filters = $this->statementFilters();
        $data = $this->service->loadStatement($role, $partyId, $from, $to, $filters);
        $page = max(1, (int) Request::get('page', 1));
        $perPage = min(100, max(25, (int) Request::get('per_page', 50)));
        $pagination = paginate_rows($data['rows'] ?? [], $page, $perPage);

        Response::view('pages/parties/party_statement', [
            'party_id' => $data['partyId'],
            'party' => $data['party'],
            'from' => $data['from'],
            'to' => $data['to'],
            'opening' => $data['opening'],
            'closing' => $data['closing'],
            'totals' => $data['totals'] ?? [],
            'filters' => $filters,
            'doc_type_options' => $this->docTypeOptions($role),
            'rows' => $pagination['items'],
            'page' => $pagination['page'],
            'per_page' => $pagination['per_page'],
            'total_pages' => $pagination['total_pages'],
            'total_rows' => $pagination['total_rows'],
            'from_row' => $pagination['from'],
            'to_row' => $pagination['to'],
            'page_title' => $role === 'supplier' ? 'كشف حساب المورد' : 'كشف حساب العميل',
            'role' => $role,
            'breadcrumbs' => $role === 'supplier'
                ? [
                    ['label'=>'لوحة التحكم','url'=>'/admin/dashboard'],
                    ['label'=>'المشتريات','url'=>'/purchases'],
                    ['label'=>'كشف حساب المورد'],
                ]
                : [
                    ['label'=>'لوحة التحكم','url'=>'/admin/dashboard'],
                    ['label'=>'المبيعات','url'=>'/sales'],
                    ['label'=>'كشف حساب العميل'],
                ],
        ]);
    }

    public function print(): void
    {
        Auth::requireLogin();
        $format = strtolower(trim((string) Request::get('format', '')));
        $settingsService = new PrintSettingsService(db());
        $preferredFormat = $settingsService->preferredDocumentFormat('party_statement', ['a4']);
        if ($format === '') {
            $basePath = Request::path();
            $qs = $_GET;
            $qs['format'] = 'a4';
            $a4Url = $basePath . '?' . http_build_query($qs);
            $role = $this->normalizeRole((string) Request::get('role', 'customer'));
            $title = $role === 'supplier' ? 'طباعة كشف حساب مورد' : 'طباعة كشف حساب عميل';
            print_format_chooser($a4Url, '', $title, $preferredFormat);
            return;
        }

        if ($format !== 'a4') {
            $format = 'a4';
        }

        $role = $this->normalizeRole((string) Request::get('role', 'customer'));
        require_permission($role === 'supplier' ? 'suppliers.statement.view' : 'sales.statement.view');
        $this->service->ensureSchema();

        $partyId = (int) Request::get('party_id', Request::get($role === 'supplier' ? 'supplier_id' : 'customer_id', 0));
        if ($partyId <= 0) {
            http_response_code(400);
            exit('Invalid');
        }

        try {
            $data = $this->service->printPayload(
                $role,
                $partyId,
                trim((string) Request::get('from', '')),
                trim((string) Request::get('to', '')),
                $this->statementFilters(),
            );
            $payload = $settingsService->preparePrintPayload('party_statement', [
                'title' => $data['title'],
                'meta' => $data['meta'],
                'company' => $data['company'],
                'footer' => $data['footer'],
                'party' => $data['party'],
                'from' => $data['from'],
                'to' => $data['to'],
                'opening' => $data['opening'],
                'closing' => $data['closing'],
                'totals' => $data['totals'] ?? [],
                'rows' => $data['rows'],
                'role' => $data['role'],
                'print_format' => $format,
                'print_template' => 'purchase_invoice_v2',
            ]);
            print_view($format, 'party_statement', $payload);
        } catch (\RuntimeException $e) {
            http_response_code($e->getMessage() === 'Not found' ? 404 : 400);
            exit($e->getMessage());
        }
    }


    public function exportPdf(): void
    {
        Auth::requireLogin();
        $role = $this->normalizeRole((string) Request::get('role', 'customer'));
        require_permission($role === 'supplier' ? 'suppliers.statement.view' : 'sales.statement.view');
        $this->service->ensureSchema();

        $partyId = (int) Request::get('party_id', Request::get($role === 'supplier' ? 'supplier_id' : 'customer_id', 0));
        if ($partyId <= 0) {
            http_response_code(400);
            exit('Invalid party');
        }

        try {
            $settingsService = new PrintSettingsService(db());
            $data = $this->service->printPayload(
                $role,
                $partyId,
                trim((string) Request::get('from', '')),
                trim((string) Request::get('to', '')),
                $this->statementFilters(),
            );
            $payload = $settingsService->preparePrintPayload('party_statement', [
                'title' => $data['title'],
                'meta' => $data['meta'],
                'company' => $data['company'],
                'footer' => $data['footer'],
                'party' => $data['party'],
                'from' => $data['from'],
                'to' => $data['to'],
                'opening' => $data['opening'],
                'closing' => $data['closing'],
                'totals' => $data['totals'] ?? [],
                'rows' => $data['rows'],
                'role' => $data['role'],
                'print_format' => 'a4',
                'print_template' => 'purchase_invoice_v2',
            ]);
            $html = $this->renderStatementPrintHtml($payload);
            $pdfBytes = $this->generatePdfBytes($html);
            if ($pdfBytes === '') {
                http_response_code(500);
                header('Content-Type: text/plain; charset=UTF-8');
                echo 'تعذر إنشاء ملف PDF على هذا السيرفر. تأكد من توفر WeasyPrint أو Chrome/Edge Headless على السيرفر.';
                return;
            }

            $party = is_array($data['party'] ?? null) ? $data['party'] : [];
            $code = preg_replace('/[^A-Za-z0-9._-]+/', '-', (string)($party['code'] ?? $partyId));
            $filename = 'statement-' . ($role === 'supplier' ? 'supplier-' : 'customer-') . $code . '-' . date('Ymd-His') . '.pdf';
            header('Content-Type: application/pdf');
            header('Content-Disposition: attachment; filename="' . $filename . '"');
            header('Content-Length: ' . strlen($pdfBytes));
            echo $pdfBytes;
            exit;
        } catch (\RuntimeException $e) {
            http_response_code($e->getMessage() === 'Not found' ? 404 : 400);
            exit($e->getMessage());
        }
    }


    public function exportExcel(): void
    {
        Auth::requireLogin();
        $role = $this->normalizeRole((string) Request::get('role', 'customer'));
        require_permission($role === 'supplier' ? 'suppliers.statement.view' : 'sales.statement.view');
        $this->service->ensureSchema();

        $partyId = (int) Request::get('party_id', Request::get($role === 'supplier' ? 'supplier_id' : 'customer_id', 0));
        if ($partyId <= 0) {
            http_response_code(400);
            exit('Invalid party');
        }

        $filters = $this->statementFilters();
        $data = $this->service->loadStatement(
            $role,
            $partyId,
            trim((string) Request::get('from', '')),
            trim((string) Request::get('to', '')),
            $filters
        );
        if (($data['party'] ?? null) === null) {
            http_response_code(404);
            exit('Not found');
        }

        $party = is_array($data['party']) ? $data['party'] : [];
        $title = $role === 'supplier' ? 'كشف حساب مورد' : 'كشف حساب عميل';
        $fileName = 'statement-' . preg_replace('/[^A-Za-z0-9_-]+/', '-', (string)($party['code'] ?? $partyId)) . '-' . date('Ymd-His') . '.xls';

        header('Content-Type: application/vnd.ms-excel; charset=UTF-8');
        header('Content-Disposition: attachment; filename="' . $fileName . '"');
        header('Cache-Control: max-age=0');
        echo "\xEF\xBB\xBF";

        $rows = is_array($data['rows'] ?? null) ? $data['rows'] : [];
        $running = (float)($data['opening'] ?? 0);
        $totals = is_array($data['totals'] ?? null) ? $data['totals'] : [];
        ?>
<!doctype html>
<html lang="ar" dir="rtl">
<head>
<meta charset="utf-8">
<style>
  body{font-family:Arial, sans-serif;direction:rtl;} table{border-collapse:collapse;width:100%;direction:rtl;} th,td{border:1px solid #cbd5e1;padding:6px;text-align:center;} th{background:#16324a;color:#fff;font-weight:bold;} .title{font-size:18px;font-weight:bold;} .summary td{font-weight:bold;background:#f8fafc;}
</style>
</head>
<body>
<table>
  <tr><td colspan="7" class="title"><?= e($title) ?></td></tr>
  <tr><td>الطرف</td><td colspan="2"><?= e((string)($party['name'] ?? '')) ?></td><td>الكود</td><td><?= e((string)($party['code'] ?? '')) ?></td><td>الفترة</td><td><?= e((string)($data['from'] ?? '')) ?> - <?= e((string)($data['to'] ?? '')) ?></td></tr>
  <tr class="summary"><td>رصيد أول المدة</td><td><?= e(format_money($data['opening'] ?? 0)) ?></td><td>إجمالي مدين</td><td><?= e(format_money($totals['debit'] ?? 0)) ?></td><td>إجمالي دائن</td><td><?= e(format_money($totals['credit'] ?? 0)) ?></td><td>رصيد آخر المدة: <?= e(format_money($data['closing'] ?? $running)) ?></td></tr>
</table>
<br>
<table>
  <thead><tr><th>#</th><th>التاريخ</th><th>النوع</th><th>رقم المستند</th><th>البيان</th><th>مدين</th><th>دائن</th><th>الرصيد</th></tr></thead>
  <tbody>
    <tr><td>0</td><td><?= e((string)($data['from'] ?? '')) ?></td><td><?= e(label_def('OPENING_BALANCE', 'statement_doc_type')) ?></td><td></td><td>رصيد أول المدة</td><td></td><td></td><td><?= e(format_money($running)) ?></td></tr>
    <?php $i=0; foreach ($rows as $r): $i++; $d=(float)($r['debit'] ?? 0); $c=(float)($r['credit'] ?? 0); $running += $d - $c; ?>
      <tr>
        <td><?= $i ?></td>
        <td><?= e((string)($r['doc_date'] ?? '')) ?></td>
        <td><?= e(label_def((string)($r['doc_type'] ?? ''), 'statement_doc_type')) ?></td>
        <td><?= e((string)($r['doc_no'] ?? '')) ?></td>
        <td><?= e(ledger_entry_description($r)) ?></td>
        <td><?= $d ? e(format_money($d)) : '' ?></td>
        <td><?= $c ? e(format_money($c)) : '' ?></td>
        <td><?= e(format_money($running)) ?></td>
      </tr>
    <?php endforeach; ?>
  </tbody>
</table>
</body>
</html>
        <?php
    }

    private function renderStatementPrintHtml(array $payload): string
    {
        extract($payload, EXTR_SKIP);
        $title = (string)($payload['title'] ?? 'كشف حساب');
        $bodyFile = APP_PATH . '/views/print/bodies/party_statement.php';
        ob_start();
        require $bodyFile;
        $body = (string)ob_get_clean();

        $css = '';
        foreach ([
            APP_BASE_PATH . '/public/assets/print/print_a4.css',
            APP_BASE_PATH . '/public/assets/print/purchase_invoice_a4.css',
        ] as $cssFile) {
            if (is_file($cssFile)) {
                $css .= "\n" . (string)file_get_contents($cssFile);
            }
        }

        ob_start();
        require APP_PATH . '/views/print/partials/header.php';
        $header = (string)ob_get_clean();
        ob_start();
        require APP_PATH . '/views/print/partials/footer.php';
        $footerHtml = (string)ob_get_clean();

        $pdfCss = <<<'CSS'
/* PDF export must match the normal print preview page, not the browser screen viewport. */
@page { size: A4 portrait; margin: 0; }
html,body{width:210mm!important;margin:0!important;padding:0!important;background:#fff!important;overflow:visible!important;}
body{font-family:"Cairo","DejaVu Sans",Arial,sans-serif!important;-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important;color-adjust:exact!important;}
.print-toolbar,.no-print{display:none!important;}
.page-shell{width:210mm!important;max-width:210mm!important;margin:0!important;padding:0!important;background:#fff!important;}
.print-page-frame{display:none!important;}
.page.a4{width:210mm!important;max-width:210mm!important;min-height:0!important;margin:0!important;border:none!important;border-radius:0!important;box-shadow:none!important;overflow:visible!important;background:#fff!important;padding:0!important;}
.page.a4::before{height:66px!important;}
.page-content{min-height:0!important;padding:4mm 8mm 5mm!important;box-sizing:border-box!important;display:block!important;}
.print-body{display:block!important;flex:none!important;padding-top:0!important;}
.print-footer{margin-top:5mm!important;break-inside:avoid!important;page-break-inside:avoid!important;}
.purchase-header{margin-bottom:8px!important;padding-bottom:8px!important;}
.purchase-header-grid{grid-template-columns:1.18fr .82fr!important;gap:10px!important;}
.purchase-brand-card{padding:8px 14px!important;}
.purchase-logo{width:72px!important;height:72px!important;flex-basis:72px!important;}
.purchase-company-name{font-size:21px!important;}
.purchase-company-sub,.purchase-company-meta{font-size:10px!important;line-height:1.45!important;}
.purchase-doc-card{padding:6px 10px!important;}
.purchase-doc-row{padding:3px 0!important;font-size:11px!important;}
.pi-hero{padding-bottom:6px!important;}
.pi-title{font-size:24px!important;}
.pi-title-en{font-size:10px!important;margin-top:2px!important;}
.statement-invoice-grid,.statement-hero-grid{grid-template-columns:1.15fr .85fr!important;gap:10px!important;margin-bottom:8px!important;}
.statement-summary-stats{grid-template-columns:1fr 1fr!important;padding:9px!important;gap:8px!important;}
.statement-summary-stats .pi-stat-box{min-height:58px!important;padding:8px!important;}
.statement-summary-stats .pi-stat-box strong{font-size:16px!important;}
.statement-meta-grid{grid-template-columns:repeat(3,minmax(0,1fr))!important;gap:7px!important;padding-bottom:10px!important;}
.pi-party-name{font-size:17px!important;padding-top:10px!important;padding-bottom:4px!important;}
.pi-card-head{padding:7px 10px!important;}
.pi-table th{padding:6px 6px!important;font-size:9.5px!important;}
.pi-table td{padding:6px 6px!important;font-size:9.6px!important;line-height:1.35!important;}
.statement-table-wrap,.pi-table-wrap{width:100%!important;}
.statement-page{margin-bottom:7px!important;}
.statement-page-follow{padding-top:7mm!important;}
.statement-bottom-grid{margin-top:7px!important;}
.pi-total-table td{padding:6px 10px!important;font-size:11px!important;}
.pi-total-table tr.is-grand td{padding-top:8px!important;padding-bottom:8px!important;font-size:13px!important;}
.pf-company-strip{margin-top:0!important;padding-top:5px!important;}
.pf-company-name{font-size:10px!important;}
.pf-company-address,.pf-company-contact,.pf-bottom{font-size:8px!important;line-height:1.3!important;}
@media print{
  @page { size: A4 portrait; margin:0; }
  html,body{width:210mm!important;}
  .page.a4{width:210mm!important;max-width:210mm!important;min-height:0!important;}
  .page-content{min-height:0!important;padding:4mm 8mm 5mm!important;display:block!important;}
}
CSS;
        $html = '<!doctype html><html lang="ar" dir="rtl"><head><meta charset="utf-8"><title>' . e($title) . '</title>'
            . '<style>' . $css . "
" . $pdfCss . '</style>'
            . '</head><body><div class="page a4"><div class="page-content">'
            . $header . '<div class="print-body">' . $body . '</div>' . $footerHtml
            . '</div></div></body></html>';

        return $this->localizePdfAssetUrls($html);
    }

    private function localizePdfAssetUrls(string $html): string
    {
        $publicPath = realpath(APP_BASE_PATH . '/public');
        if ($publicPath === false) {
            return $html;
        }
        $publicPath = rtrim(str_replace('\\', '/', $publicPath), '/');
        $baseUri = rtrim((string)base_uri(), '/');

        return (string)preg_replace_callback('/\b(src|href)=(["\'])([^"\']+)\2/i', static function (array $m) use ($publicPath, $baseUri): string {
            $attr = $m[1];
            $quote = $m[2];
            $url = html_entity_decode((string)$m[3], ENT_QUOTES | ENT_HTML5, 'UTF-8');
            if ($url === '' || preg_match('#^(?:https?:|data:|file:)#i', $url) === 1) {
                return $m[0];
            }

            $path = parse_url($url, PHP_URL_PATH);
            if (!is_string($path) || $path === '') {
                return $m[0];
            }
            $path = rawurldecode($path);
            if ($baseUri !== '' && str_starts_with($path, $baseUri . '/')) {
                $path = substr($path, strlen($baseUri) + 1);
            } else {
                $path = ltrim($path, '/');
            }

            $local = realpath($publicPath . '/' . ltrim($path, '/'));
            if ($local === false || !is_file($local)) {
                return $m[0];
            }
            $local = str_replace('\\', '/', $local);
            $fileUrl = 'file:///' . ltrim($local, '/');
            if (DIRECTORY_SEPARATOR === '\\') {
                $fileUrl = 'file:///' . $local;
            }
            $fileUrl = str_replace(' ', '%20', $fileUrl);

            return $attr . '=' . $quote . htmlspecialchars($fileUrl, ENT_QUOTES | ENT_HTML5, 'UTF-8') . $quote;
        }, $html);
    }

    private function generatePdfBytes(string $html): string
    {
        $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'statement_pdf_' . bin2hex(random_bytes(8));
        if (!@mkdir($tmpDir, 0777, true) && !is_dir($tmpDir)) {
            return '';
        }
        $htmlFile = $tmpDir . DIRECTORY_SEPARATOR . 'statement.html';
        $pdfFile = $tmpDir . DIRECTORY_SEPARATOR . 'statement.pdf';
        $scriptFile = $tmpDir . DIRECTORY_SEPARATOR . 'render_pdf.py';
        file_put_contents($htmlFile, $html);
        $bytes = '';

        try {
            file_put_contents($scriptFile, <<<'PY'
from pathlib import Path
from weasyprint import HTML
import sys
html_path = Path(sys.argv[1])
pdf_path = Path(sys.argv[2])
HTML(filename=str(html_path), base_url=str(html_path.parent)).write_pdf(str(pdf_path))
PY);
            if ($this->runCommand($this->buildPythonWeasyCommand($scriptFile, $htmlFile, $pdfFile)) && is_file($pdfFile)) {
                $bytes = (string)file_get_contents($pdfFile);
            }
            if ($bytes === '') {
                @unlink($pdfFile);
                if ($this->runCommand($this->buildHeadlessBrowserCommand($htmlFile, $pdfFile)) && is_file($pdfFile)) {
                    $bytes = (string)file_get_contents($pdfFile);
                }
            }
        } finally {
            @unlink($htmlFile);
            @unlink($pdfFile);
            @unlink($scriptFile);
            @rmdir($tmpDir);
        }
        return $bytes;
    }

    private function buildPythonWeasyCommand(string $scriptFile, string $htmlFile, string $pdfFile): ?string
    {
        $python = $this->findExecutable(['python3', 'python']);
        if ($python === null) {
            return null;
        }
        return $this->quoteExecutable($python) . ' ' . escapeshellarg($scriptFile) . ' ' . escapeshellarg($htmlFile) . ' ' . escapeshellarg($pdfFile) . ' 2>&1';
    }

    private function buildHeadlessBrowserCommand(string $htmlFile, string $pdfFile): ?string
    {
        $browser = $this->findExecutable($this->headlessBrowserCandidates());
        if ($browser === null) {
            return null;
        }
        $htmlUrl = 'file:///' . str_replace(DIRECTORY_SEPARATOR, '/', ltrim($htmlFile, DIRECTORY_SEPARATOR));
        if (DIRECTORY_SEPARATOR === '\\') {
            $htmlUrl = 'file:///' . str_replace('\\', '/', $htmlFile);
        }
        return $this->quoteExecutable($browser)
            . ' --headless=new --disable-gpu --disable-dev-shm-usage --allow-file-access-from-files'
            . ' --no-pdf-header-footer --print-to-pdf-no-header --disable-features=PrintBrowser'
            . ' --window-size=794,1123 --force-device-scale-factor=1'
            . ' --print-to-pdf=' . escapeshellarg($pdfFile)
            . ' ' . escapeshellarg($htmlUrl) . ' 2>&1';
    }

    private function runCommand(?string $command): bool
    {
        if ($command === null || trim($command) === '') {
            return false;
        }
        $output = [];
        $code = 1;
        @exec($command, $output, $code);
        return $code === 0;
    }

    private function findExecutable(array $candidates): ?string
    {
        foreach ($candidates as $candidate) {
            if ($candidate === '') {
                continue;
            }
            if (is_file($candidate)) {
                return $candidate;
            }
            $cmd = strtoupper(substr(PHP_OS_FAMILY, 0, 3)) === 'WIN'
                ? 'where ' . escapeshellarg($candidate) . ' 2>NUL'
                : 'command -v ' . escapeshellarg($candidate) . ' 2>/dev/null';
            $result = @shell_exec($cmd);
            if (is_string($result) && trim($result) !== '') {
                $line = trim(strtok($result, "\n"));
                if ($line !== '') {
                    return $line;
                }
            }
        }
        return null;
    }

    private function quoteExecutable(string $path): string
    {
        return '"' . str_replace('"', '\\"', $path) . '"';
    }

    private function headlessBrowserCandidates(): array
    {
        return [
            'chrome', 'google-chrome', 'chromium', 'chromium-browser', 'msedge',
            'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
            'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
            'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
            'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
        ];
    }


    private function statementFilters(): array
    {
        return [
            'doc_type' => trim((string) Request::get('doc_type', '')),
            'doc_no' => trim((string) Request::get('doc_no', '')),
            'direction' => trim((string) Request::get('direction', '')),
            'amount_min' => trim((string) Request::get('amount_min', '')),
            'amount_max' => trim((string) Request::get('amount_max', '')),
            'q' => trim((string) Request::get('q', '')),
        ];
    }


    private function docTypeOptions(string $role): array
    {
        $common = [
            'RCPT' => label_def('RCPT', 'statement_doc_type'),
            'CHECK_RECEIVE' => label_def('CHECK_RECEIVE', 'statement_doc_type'),
            'PAY' => label_def('PAY', 'statement_doc_type'),
        ];
        if ($role === 'supplier') {
            return [
                'PURCHASE_INV' => label_def('PURCHASE_INV', 'statement_doc_type'),
                'PURCHASE_PAY' => label_def('PURCHASE_PAY', 'statement_doc_type'),
                'PURCHASE_RET' => label_def('PURCHASE_RET', 'statement_doc_type'),
                'PAYMENT' => label_def('PAYMENT', 'statement_doc_type'),
                'SUPPLIER_PAYMENT' => label_def('SUPPLIER_PAYMENT', 'statement_doc_type'),
            ] + $common;
        }
        return [
            'SALES_INV' => label_def('SALES_INV', 'statement_doc_type'),
            'SALES_RCPT' => label_def('SALES_RCPT', 'statement_doc_type'),
            'SALES_RET' => label_def('SALES_RET', 'statement_doc_type'),
            'POS_SALES_INV' => label_def('POS_SALES_INV', 'statement_doc_type'),
        ] + $common;
    }

    private function normalizeRole(string $role): string
    {
        return strtolower(trim($role)) === 'supplier' ? 'supplier' : 'customer';
    }
}
