<?php

declare(strict_types=1);

namespace ABS\Modules\Reports\Controllers;

use ABS\Core\Controller;
use ABS\Modules\Reports\Repositories\SupplierStatisticsRepository;
use ABS\Modules\Reports\Services\SupplierAccountImportService;
use ABS\Modules\Reports\Services\SagaPurchaseImportService;
use ABS\Modules\Reports\Repositories\SupplierReconciliationRepository;
use RuntimeException;
use ZipArchive;

final class ReportsController extends Controller
{
    private SupplierStatisticsRepository $repository;
    private SupplierAccountImportService $importer;
    private SagaPurchaseImportService $purchaseImporter;
    private SupplierReconciliationRepository $reconciliation;

    public function __construct()
    {
        $this->repository = new SupplierStatisticsRepository();
        $this->importer = new SupplierAccountImportService();
        $this->purchaseImporter = new SagaPurchaseImportService();
        $this->reconciliation = new SupplierReconciliationRepository();
    }

    public function index(): never
    {
        $this->view('reports/index', [
            'title' => 'Rapoarte',
            'supplierSummary' => $this->repository->summary(
                (int) date('Y')
            ),
        ]);
    }

    public function suppliers(): never
    {
        $year = $this->year($_GET['year'] ?? date('Y'));
        $suppliers = $this->repository->suppliers();

        foreach ($suppliers as &$supplier) {
            $supplier['statistics'] =
                $this->repository->supplierStatistics(
                    (int) $supplier['id'],
                    $year
                );
        }
        unset($supplier);

        $this->view('reports/supplier_statistics', [
            'title' => 'Statistici furnizori',
            'suppliers' => $suppliers,
            'year' => $year,
            'summary' => $this->repository->summary($year),
            'success' => $_SESSION['reports_success'] ?? null,
            'error' => $_SESSION['reports_error'] ?? null,
        ]);

        unset(
            $_SESSION['reports_success'],
            $_SESSION['reports_error']
        );
    }

    public function supplier(string $id): never
    {
        $supplierId = (int) $id;
        $supplier = $this->repository->supplier($supplierId);

        if ($supplier === null) {
            throw new RuntimeException(
                'Furnizorul nu a fost găsit.'
            );
        }

        $year = $this->year($_GET['year'] ?? date('Y'));
        $month = max(0, min(12, (int) ($_GET['month'] ?? 0)));
        $asOfDate = $this->date(
            $_GET['as_of_date'] ?? date('Y-m-d')
        );
        $weeks = max(1, min(14, (int) ($_GET['weeks'] ?? 8)));
        $startDate = $this->date(
            $_GET['start_date'] ?? $asOfDate
        );

        $this->view('reports/supplier_dashboard', [
            'title' => 'Statistici ' . $supplier['name'],
            'supplier' => $supplier,
            'year' => $year,
            'month' => $month,
            'asOfDate' => $asOfDate,
            'weeks' => $weeks,
            'startDate' => $startDate,
            'statistics' =>
                $this->repository->supplierStatisticsFiltered(
                    $supplierId,
                    $year,
                    $month,
                    $asOfDate
                ),
            'monthly' =>
                $this->repository->monthlyStatistics(
                    $supplierId,
                    $year
                ),
            'weeklyPayments' =>
                $this->repository->weeklyPaymentSchedule(
                    $supplierId,
                    $startDate,
                    $weeks
                ),
            'overdue' =>
                $this->repository->overdueAmount(
                    $supplierId,
                    $startDate
                ),
            'targets' =>
                $this->repository->targets(
                    $supplierId,
                    $year
                ),
            'imports' =>
                $this->repository->importsForYear(
                    $supplierId,
                    $year
                ),
            'monthlyChecklist' =>
                $this->repository->monthlyChecklist(
                    $supplierId,
                    $year
                ),
            'creditSnapshot' =>
                $this->repository->latestCreditSnapshot(
                    $supplierId,
                    $asOfDate
                ),
            'currentYear' => (int) date('Y'),
            'currentMonth' => (int) date('n'),
            'success' => $_SESSION['reports_success'] ?? null,
            'error' => $_SESSION['reports_error'] ?? null,
        ]);

        unset(
            $_SESSION['reports_success'],
            $_SESSION['reports_error']
        );
    }

    public function importSupplier(string $id): never
    {
        $supplierId = (int) $id;
        $supplier = $this->repository->supplier($supplierId);

        if ($supplier === null) {
            throw new RuntimeException('Furnizorul nu a fost găsit.');
        }

        $year = $this->year($_POST['period_year'] ?? date('Y'));
        $month = max(1, min(12, (int) ($_POST['period_month'] ?? date('n'))));
        $coverage = (string) ($_POST['coverage_type'] ?? 'full');
        if (!in_array($coverage, ['full', 'partial'], true)) {
            $coverage = 'full';
        }

        try {
            $fileBag = $_FILES['account_files'] ?? $_FILES['account_file'] ?? null;
            if (!is_array($fileBag) || empty($fileBag['name'])) {
                throw new RuntimeException('Selectează cel puțin un fișier pentru import.');
            }

            $names = is_array($fileBag['name']) ? $fileBag['name'] : [$fileBag['name']];
            $tmpNames = is_array($fileBag['tmp_name']) ? $fileBag['tmp_name'] : [$fileBag['tmp_name']];
            $errors = is_array($fileBag['error'] ?? null) ? $fileBag['error'] : [($fileBag['error'] ?? UPLOAD_ERR_OK)];

            $totalFiles = 0;
            $totalDocuments = 0;
            $totalPayments = 0;
            $balanceFiles = 0;
            $messages = [];

            foreach ($names as $i => $originalName) {
                $originalName = (string) $originalName;
                $tmp = (string) ($tmpNames[$i] ?? '');
                $error = (int) ($errors[$i] ?? UPLOAD_ERR_NO_FILE);

                if ($error === UPLOAD_ERR_NO_FILE || $originalName === '') {
                    continue;
                }
                if ($error !== UPLOAD_ERR_OK || !is_uploaded_file($tmp)) {
                    throw new RuntimeException('Upload eșuat pentru ' . $originalName . '.');
                }

                $result = $this->importer->parse(
                    (string) ($supplier['parser_code'] ?? ''),
                    $tmp,
                    $originalName
                );

                // Pentru Materom perioada este preluată din fișier, dacă există.
                $fileYear = $year;
                $fileMonth = $month;
                if (!empty($result['period_from'])) {
                    $fileYear = (int) substr((string) $result['period_from'], 0, 4);
                    $fileMonth = (int) substr((string) $result['period_from'], 5, 2);
                }

                $storedPath = $this->storeSupplierFile(
                    $supplierId,
                    $fileYear,
                    $fileMonth,
                    $tmp,
                    $originalName
                );

                $rowCount = count($result['rows'] ?? [])
                    + count($result['payments'] ?? [])
                    + (!empty($result['balance']) ? 1 : 0);

                $importId = $this->repository->createImport([
                    'supplier_id' => $supplierId,
                    'original_filename' => $originalName,
                    'stored_file_path' => $storedPath,
                    'parser_code' => (string) ($supplier['parser_code'] ?? ''),
                    'imported_rows' => $rowCount,
                    'skipped_rows' => (int) ($result['skipped'] ?? 0),
                    'period_year' => $fileYear,
                    'period_month' => $fileMonth,
                    'coverage_type' => $coverage,
                    'period_from' => $result['period_from'] ?? null,
                    'period_to' => $result['period_to'] ?? null,
                    'notes' => trim((string) ($_POST['notes'] ?? '')) ?: null,
                    'created_by' => (int) ($_SESSION['user']['id'] ?? 0) ?: null,
                ]);

                if (!empty($result['rows'])) {
                    $saved = $this->repository->upsertDocuments(
                        $supplierId,
                        $importId,
                        $result['rows']
                    );
                    $totalDocuments += $saved;
                }

                if (!empty($result['payments'])) {
                    $totalPayments += $this->repository->saveMateromPayments(
                        $supplierId,
                        $importId,
                        $result['payments']
                    );
                }

                if (!empty($result['balance'])) {
                    $this->repository->saveMateromBalance(
                        $supplierId,
                        $importId,
                        $result['balance']
                    );
                    $balanceFiles++;
                }

                $this->repository->markChecklistUploaded(
                    $supplierId,
                    $fileYear,
                    $fileMonth,
                    $importId
                );

                $totalFiles++;
                $messages[] = $originalName;
            }

            if ($totalFiles === 0) {
                throw new RuntimeException('Nu a fost încărcat niciun fișier.');
            }

            unset($_SESSION['reports_error']);
            $_SESSION['reports_success'] =
                'Import finalizat: ' . $totalFiles . ' fișier(e) arhivate, '
                . $totalDocuments . ' facturi, '
                . $totalPayments . ' plăți și '
                . $balanceFiles . ' situație/situații de sold.';
        } catch (\Throwable $exception) {
            unset($_SESSION['reports_success']);
            $_SESSION['reports_error'] = $exception->getMessage();
        }

        $this->redirect(
            '/reports/suppliers/' . $supplierId . '?year=' . $year
        );
    }

    public function saveTarget(string $id): never
    {
        $supplierId = (int) $id;
        $year = $this->year(
            $_POST['target_year'] ?? date('Y')
        );

        try {
            $month = (int) ($_POST['target_month'] ?? 0);

            if ($month < 0 || $month > 12) {
                $month = 0;
            }

            $this->repository->saveTarget([
                'supplier_id' => $supplierId,
                'target_year' => $year,
                'target_month' => $month === 0 ? null : $month,
                'purchase_target' => $this->decimal(
                    $_POST['purchase_target'] ?? 0
                ),
                'payment_target' => $this->decimal(
                    $_POST['payment_target'] ?? 0
                ),
                'notes' => trim((string) (
                    $_POST['target_notes'] ?? ''
                )) ?: null,
            ]);

            $_SESSION['reports_success'] =
                'Targetul a fost salvat.';
        } catch (\Throwable $exception) {
            $_SESSION['reports_error'] =
                $exception->getMessage();
        }

        $this->redirect(
            '/reports/suppliers/'
            . $supplierId
            . '?year='
            . $year
        );
    }

    public function updateChecklist(string $id): never
    {
        $supplierId = (int) $id;
        $year = $this->year(
            $_POST['checklist_year'] ?? date('Y')
        );
        $month = max(
            1,
            min(12, (int) ($_POST['checklist_month'] ?? date('n')))
        );

        try {
            $this->repository->saveChecklist([
                'supplier_id' => $supplierId,
                'checklist_year' => $year,
                'checklist_month' => $month,
                'is_checked' => !empty($_POST['is_checked']) ? 1 : 0,
                'is_sent_accounting' =>
                    !empty($_POST['is_sent_accounting']) ? 1 : 0,
                'is_reconciled' =>
                    !empty($_POST['is_reconciled']) ? 1 : 0,
                'notes' => trim((string) (
                    $_POST['checklist_notes'] ?? ''
                )) ?: null,
                'updated_by' => (int) (
                    $_SESSION['user']['id'] ?? 0
                ) ?: null,
            ]);

            $_SESSION['reports_success'] =
                'Checklistul lunii a fost actualizat.';
        } catch (\Throwable $exception) {
            $_SESSION['reports_error'] =
                $exception->getMessage();
        }

        $this->redirect(
            '/reports/suppliers/'
            . $supplierId
            . '?year='
            . $year
        );
    }

    public function downloadImport(
        string $supplierId,
        string $importId
    ): never {
        $import = $this->repository->import(
            (int) $importId,
            (int) $supplierId
        );

        if ($import === null) {
            throw new RuntimeException(
                'Fișierul importat nu a fost găsit.'
            );
        }

        $this->streamStoredFile($import);
    }

    public function bulkDownload(string $id): never
    {
        $supplierId = (int) $id;
        $ids = array_values(array_filter(array_map(
            'intval',
            (array) ($_POST['import_ids'] ?? [])
        )));

        if ($ids === []) {
            $_SESSION['reports_error'] =
                'Selectează cel puțin o fișă.';
            $this->redirect(
                '/reports/suppliers/' . $supplierId
            );
        }

        if (!class_exists(ZipArchive::class)) {
            throw new RuntimeException(
                'Extensia ZIP nu este activă în PHP.'
            );
        }

        $imports = $this->repository->importsByIds(
            $supplierId,
            $ids
        );

        $temporary = tempnam(
            sys_get_temp_dir(),
            'supplier-files-'
        );

        if ($temporary === false) {
            throw new RuntimeException(
                'Arhiva temporară nu poate fi creată.'
            );
        }

        $zip = new ZipArchive();

        if (
            $zip->open(
                $temporary,
                ZipArchive::CREATE
                | ZipArchive::OVERWRITE
            ) !== true
        ) {
            @unlink($temporary);
            throw new RuntimeException(
                'Arhiva ZIP nu poate fi creată.'
            );
        }

        foreach ($imports as $import) {
            $absolute = $this->absoluteStoredPath(
                (string) $import['stored_file_path']
            );

            if (!is_file($absolute)) {
                continue;
            }

            $archiveName = sprintf(
                '%04d-%02d_%s',
                (int) $import['period_year'],
                (int) $import['period_month'],
                basename((string) $import['original_filename'])
            );

            $zip->addFile($absolute, $archiveName);
        }

        $zip->close();

        header('Content-Type: application/zip');
        header(
            'Content-Disposition: attachment; filename="fise-furnizor-'
            . date('Ymd-His')
            . '.zip"'
        );
        header('Content-Length: ' . filesize($temporary));
        readfile($temporary);
        @unlink($temporary);
        exit;
    }

    public function saveCreditSnapshot(string $id): never
    {
        $supplierId = (int) $id;
        $year = $this->year($_POST['return_year'] ?? date('Y'));
        $month = max(
            0,
            min(12, (int) ($_POST['return_month'] ?? 0))
        );
        $snapshotDate = $this->date(
            $_POST['snapshot_date'] ?? date('Y-m-d')
        );

        try {
            $total = $this->decimal(
                $_POST['credit_limit_total'] ?? 0
            );
            $available = $this->decimal(
                $_POST['available_credit'] ?? 0
            );

            if ($total < 0 || $available < 0) {
                throw new RuntimeException(
                    'Valorile limitei de credit nu pot fi negative.'
                );
            }

            $this->repository->saveCreditSnapshot([
                'supplier_id' => $supplierId,
                'snapshot_date' => $snapshotDate,
                'credit_limit_total' => $total,
                'available_credit' => $available,
                'notes' => trim((string) (
                    $_POST['credit_notes'] ?? ''
                )) ?: null,
                'created_by' => (int) (
                    $_SESSION['user']['id'] ?? 0
                ) ?: null,
            ]);

            unset($_SESSION['reports_error']);
            $_SESSION['reports_success'] =
                'Situația limitei de credit a fost salvată.';
        } catch (\Throwable $exception) {
            unset($_SESSION['reports_success']);
            $_SESSION['reports_error'] =
                $exception->getMessage();
        }

        $query = http_build_query([
            'year' => $year,
            'month' => $month,
            'as_of_date' => $snapshotDate,
        ]);

        $this->redirect(
            '/reports/suppliers/'
            . $supplierId
            . '?'
            . $query
        );
    }

    public function uploadLogo(string $id): never
    {
        header(
            'Content-Type: application/json; charset=UTF-8'
        );

        try {
            $supplierId = (int) $id;
            $supplier = $this->repository->supplier(
                $supplierId
            );

            if ($supplier === null) {
                throw new RuntimeException(
                    'Furnizorul nu a fost găsit.'
                );
            }

            if (
                empty($_FILES['logo']['tmp_name'])
                || !is_uploaded_file(
                    (string) $_FILES['logo']['tmp_name']
                )
            ) {
                throw new RuntimeException(
                    'Selectează imaginea logo-ului.'
                );
            }

            $temporary = (string) $_FILES['logo']['tmp_name'];
            $mime = (new \finfo(
                FILEINFO_MIME_TYPE
            ))->file($temporary);

            $extensions = [
                'image/png' => 'png',
                'image/jpeg' => 'jpg',
                'image/webp' => 'webp',
                'image/gif' => 'gif',
                'image/svg+xml' => 'svg',
            ];

            if (!isset($extensions[$mime])) {
                throw new RuntimeException(
                    'Sunt acceptate PNG, JPG, WEBP, '
                    . 'GIF și SVG.'
                );
            }

            $directory = BASE_PATH
                . '/public/uploads/'
                . 'supplier-statistics-logos';

            if (
                !is_dir($directory)
                && !mkdir($directory, 0775, true)
                && !is_dir($directory)
            ) {
                throw new RuntimeException(
                    'Folderul logo-urilor nu poate fi creat.'
                );
            }

            $filename = 'supplier-'
                . $supplierId
                . '-'
                . date('YmdHis')
                . '-'
                . bin2hex(random_bytes(4))
                . '.'
                . $extensions[$mime];

            if (!move_uploaded_file(
                $temporary,
                $directory . '/' . $filename
            )) {
                throw new RuntimeException(
                    'Logo-ul nu a putut fi salvat.'
                );
            }

            $path =
                '/uploads/supplier-statistics-logos/'
                . $filename;

            $this->repository->updateLogo(
                $supplierId,
                $path
            );

            echo json_encode([
                'success' => true,
                'logo_path' => $path,
                'message' =>
                    'Logo-ul furnizorului a fost actualizat.',
            ], JSON_UNESCAPED_UNICODE);
        } catch (\Throwable $exception) {
            http_response_code(422);

            echo json_encode([
                'success' => false,
                'message' => $exception->getMessage(),
            ], JSON_UNESCAPED_UNICODE);
        }

        exit;
    }

    private function storeSupplierFile(
        int $supplierId,
        int $year,
        int $month,
        string $temporaryPath,
        string $originalName
    ): string {
        $extension = strtolower(pathinfo(
            $originalName,
            PATHINFO_EXTENSION
        ));

        if (
            !in_array(
                $extension,
                ['xls', 'xlsx', 'csv', 'pdf'],
                true
            )
        ) {
            throw new RuntimeException(
                'Sunt acceptate XLS, XLSX, CSV și PDF.'
            );
        }

        $directory = BASE_PATH
            . '/storage/reports/supplier-files/'
            . $supplierId
            . '/'
            . $year
            . '/'
            . str_pad((string) $month, 2, '0', STR_PAD_LEFT);

        if (
            !is_dir($directory)
            && !mkdir($directory, 0775, true)
            && !is_dir($directory)
        ) {
            throw new RuntimeException(
                'Folderul fișelor furnizorului nu poate fi creat.'
            );
        }

        $safeBase = preg_replace(
            '/[^a-zA-Z0-9._-]+/',
            '-',
            pathinfo($originalName, PATHINFO_FILENAME)
        ) ?: 'fisa';

        $filename = date('YmdHis')
            . '-'
            . bin2hex(random_bytes(3))
            . '-'
            . $safeBase
            . '.'
            . $extension;

        $destination = $directory . '/' . $filename;

        if (!copy($temporaryPath, $destination)) {
            throw new RuntimeException(
                'Fișa originală nu a putut fi arhivată.'
            );
        }

        return str_replace(
            BASE_PATH . '/',
            '',
            $destination
        );
    }

    private function streamStoredFile(array $import): never
    {
        $absolute = $this->absoluteStoredPath(
            (string) $import['stored_file_path']
        );

        if (!is_file($absolute)) {
            throw new RuntimeException(
                'Fișierul arhivat nu mai există pe disc.'
            );
        }

        $downloadName = basename(
            (string) $import['original_filename']
        );

        header('Content-Type: application/octet-stream');
        header(
            'Content-Disposition: attachment; filename="'
            . rawurlencode($downloadName)
            . '"'
        );
        header('Content-Length: ' . filesize($absolute));
        readfile($absolute);
        exit;
    }

    private function absoluteStoredPath(string $relative): string
    {
        $relative = ltrim(
            str_replace('\\', '/', $relative),
            '/'
        );

        if (
            $relative === ''
            || str_contains($relative, '..')
        ) {
            throw new RuntimeException(
                'Cale de fișier invalidă.'
            );
        }

        return BASE_PATH . '/' . $relative;
    }

    private function year(mixed $value): int
    {
        return max(2020, min(2100, (int) $value));
    }

    private function date(mixed $value): string
    {
        $date = trim((string) $value);

        if (
            !preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)
            || strtotime($date) === false
        ) {
            return date('Y-m-d');
        }

        return $date;
    }

    private function decimal(mixed $value): float
    {
        $text = str_replace(
            [' ', "\xc2\xa0"],
            '',
            trim((string) $value)
        );

        if (
            str_contains($text, ',')
            && str_contains($text, '.')
        ) {
            if (strrpos($text, ',') > strrpos($text, '.')) {
                $text = str_replace('.', '', $text);
                $text = str_replace(',', '.', $text);
            } else {
                $text = str_replace(',', '', $text);
            }
        } else {
            $text = str_replace(',', '.', $text);
        }

        return is_numeric($text)
            ? (float) $text
            : 0.0;
    }
    public function supplierReconciliation(): never
    {
        $year = $this->year($_GET['year'] ?? date('Y'));
        $month = max(0, min(12, (int)($_GET['month'] ?? 0)));
        $supplierId = (int)($_GET['supplier_id'] ?? 0);
        $importResult = $this->purchaseImporter->importPending();

        $summary = ['saga'=>[],'external'=>[],'difference'=>0,'counts'=>[]];
        $monthly = [];
        $rows = [];
        if ($supplierId > 0) {
            $summary = $this->reconciliation->supplierSummary($supplierId, $year, $month);
            $monthly = $this->reconciliation->monthlyComparison($supplierId, $year);
            $rows = $this->reconciliation->matchRows($supplierId, $year, $month);
        }

        $this->view('reports/supplier_reconciliation', [
            'title' => 'Reconciliere furnizori',
            'suppliers' => $this->reconciliation->suppliers(),
            'supplierId' => $supplierId,
            'year' => $year,
            'month' => $month,
            'summary' => $summary,
            'monthly' => $monthly,
            'rows' => $rows,
            'importErrors' => $importResult['errors'] ?? [],
        ]);
    }

    public function saveSagaSupplierAlias(): never
    {
        try {
            $supplierId = (int)($_POST['supplier_id'] ?? 0);
            $name = trim((string)($_POST['saga_supplier_name'] ?? ''));
            $taxId = trim((string)($_POST['saga_tax_id'] ?? '')) ?: null;
            if ($supplierId <= 0 || $name === '') {
                throw new RuntimeException('Furnizorul și denumirea SAGA sunt obligatorii.');
            }
            $this->reconciliation->saveAlias($supplierId, $name, $taxId);
            $_SESSION['reports_success'] = 'Asocierea furnizorului SAGA a fost salvată.';
        } catch (\Throwable $e) {
            $_SESSION['reports_error'] = $e->getMessage();
        }
        $this->redirect('/reports/suppliers/reconciliation?supplier_id=' . (int)($_POST['supplier_id'] ?? 0) . '&year=' . (int)($_POST['year'] ?? date('Y')));
    }


    public function saveSupplierReconciliationPair(): never
    {
        $supplierId=(int)($_POST['supplier_id']??0);$year=(int)($_POST['year']??date('Y'));$month=(int)($_POST['month']??0);
        try{
            $this->reconciliation->saveManualPair($supplierId,(int)($_POST['saga_line_id']??0),(int)($_POST['supplier_document_id']??0),(int)($_SESSION['user']['id']??$_SESSION['user_id']??0),trim((string)($_POST['notes']??'')));
            $_SESSION['reports_success']='Documentele au fost asociate manual.';
        }catch(\Throwable $e){$_SESSION['reports_error']=$e->getMessage();}
        $this->redirect('/reports/suppliers/reconciliation?supplier_id='.$supplierId.'&year='.$year.'&month='.$month);
    }

    public function removeSupplierReconciliationPair(): never
    {
        $supplierId=(int)($_POST['supplier_id']??0);$year=(int)($_POST['year']??date('Y'));$month=(int)($_POST['month']??0);
        $this->reconciliation->removeManualPair($supplierId,(int)($_POST['saga_line_id']??0),(int)($_POST['supplier_document_id']??0));
        $_SESSION['reports_success']='Asocierea manuală a fost anulată.';
        $this->redirect('/reports/suppliers/reconciliation?supplier_id='.$supplierId.'&year='.$year.'&month='.$month);
    }

}
