<?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 RuntimeException;
use ZipArchive;

final class ReportsController extends Controller
{
    private SupplierStatisticsRepository $repository;
    private SupplierAccountImportService $importer;

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

    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'
        );

        try {
            if (
                empty($_FILES['account_file']['tmp_name'])
                || !is_uploaded_file(
                    (string) $_FILES['account_file']['tmp_name']
                )
            ) {
                throw new RuntimeException(
                    'Selectează un fișier XLS, XLSX, CSV sau PDF.'
                );
            }

            if (!in_array($coverage, ['full', 'partial'], true)) {
                $coverage = 'full';
            }

            $currentYear = (int) date('Y');
            $currentMonth = (int) date('n');

            if (
                $coverage === 'partial'
                && !(
                    $year === $currentYear
                    && $month === $currentMonth
                )
            ) {
                throw new RuntimeException(
                    'Încărcarea parțială este permisă numai '
                    . 'pentru luna curentă.'
                );
            }

            $originalName = (string) (
                $_FILES['account_file']['name'] ?? ''
            );
            $storedPath = $this->storeSupplierFile(
                $supplierId,
                $year,
                $month,
                (string) $_FILES['account_file']['tmp_name'],
                $originalName
            );

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

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

            if (!empty($result['reset_outstanding'])) {
                $this->repository->resetOutstandingForSupplier(
                    $supplierId
                );
            }

            $saved = $this->repository->upsertDocuments(
                $supplierId,
                $importId,
                $result['rows']
            );

            $this->repository->markChecklistUploaded(
                $supplierId,
                $year,
                $month,
                $importId
            );

            unset($_SESSION['reports_error']);

            $_SESSION['reports_success'] =
                'Import finalizat pentru '
                . str_pad((string) $month, 2, '0', STR_PAD_LEFT)
                . '/'
                . $year
                . ': '
                . $saved
                . ' documente salvate.';
        } 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;
    }
}
