<?php
declare(strict_types=1);

namespace App\Support;

use App\Core\Database;
use PDO;
use Throwable;

final class ReportStore
{
    private ?PDO $pdo;
    private PdfReportService $pdfReportService;

    public function __construct()
    {
        $this->pdo = (new Database())->connection();
        $this->pdfReportService = new PdfReportService();
    }

    public function save(array $report, ?int $testSessionId = null): void
    {
        $pdfPath = $this->pdfReportService->save($report);

        if ($this->pdo instanceof PDO && $testSessionId !== null) {
            $this->saveToDatabase($report, $testSessionId, $pdfPath);
        }

        $path = $this->pathFor((string) $report['reference']);
        file_put_contents($path, json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
    }

    public function all(): array
    {
        if ($this->pdo instanceof PDO) {
            $databaseReports = $this->allFromDatabase();

            if ($databaseReports !== []) {
                return $databaseReports;
            }
        }

        $reportFiles = glob($this->directory() . '/*.json') ?: [];
        $reports = [];

        foreach ($reportFiles as $reportFile) {
            $contents = file_get_contents($reportFile);

            if ($contents === false) {
                continue;
            }

            $report = json_decode($contents, true);

            if (is_array($report)) {
                $reports[] = $report;
            }
        }

        usort($reports, static function (array $left, array $right): int {
            return strcmp((string) $right['generated_at'], (string) $left['generated_at']);
        });

        return $reports;
    }

    public function find(string $reference): ?array
    {
        if ($this->pdo instanceof PDO) {
            $databaseReport = $this->findInDatabase($reference);

            if ($databaseReport !== null) {
                return $databaseReport;
            }
        }

        $path = $this->pathFor($reference);

        if (!is_file($path)) {
            return null;
        }

        $contents = file_get_contents($path);

        if ($contents === false) {
            return null;
        }

        $report = json_decode($contents, true);

        return is_array($report) ? $report : null;
    }

    public function delete(string $reference): void
    {
        if ($this->pdo instanceof PDO) {
            $this->deleteFromDatabase($reference);
        }

        $this->pdfReportService->delete($reference);

        $path = $this->pathFor($reference);

        if (is_file($path)) {
            unlink($path);
        }
    }

    private function directory(): string
    {
        return rtrim((string) config('storage.reports'), '/\\');
    }

    private function pathFor(string $reference): string
    {
        $safeReference = preg_replace('/[^A-Za-z0-9\-]/', '', $reference) ?: 'report';
        return $this->directory() . '/' . $safeReference . '.json';
    }

    public function pdfPath(string $reference): ?string
    {
        return $this->pdfReportService->existingPath($reference);
    }

    public function ensurePdf(string $reference): ?string
    {
        $existingPath = $this->pdfReportService->existingPath($reference);

        if ($existingPath !== null) {
            return $existingPath;
        }

        $report = $this->find($reference);

        if ($report === null) {
            return null;
        }

        $pdfPath = $this->pdfReportService->save($report);

        if ($this->pdo instanceof PDO) {
            $this->updatePdfPath($reference, $pdfPath);
        }

        return $pdfPath;
    }

    public function refreshPdf(array $report): string
    {
        $pdfPath = $this->pdfReportService->save($report);

        if ($this->pdo instanceof PDO) {
            $this->updatePdfPath((string) $report['reference'], $pdfPath);
        }

        return $pdfPath;
    }

    private function saveToDatabase(array $report, int $testSessionId, ?string $pdfPath = null): void
    {
        try {
            $primaryAreaCode = (string) ($report['primary_area']['code'] ?? $report['rankings'][0]['code'] ?? 'R');
            $statement = $this->pdo->prepare(
                'INSERT INTO reports (test_session_id, reference, primary_area_code, score_json, ranking_json, report_snapshot_json, pdf_path, generated_at)
                 VALUES (:test_session_id, :reference, :primary_area_code, :score_json, :ranking_json, :report_snapshot_json, :pdf_path, :generated_at)
                 ON DUPLICATE KEY UPDATE
                    reference = VALUES(reference),
                    primary_area_code = VALUES(primary_area_code),
                    score_json = VALUES(score_json),
                    ranking_json = VALUES(ranking_json),
                    report_snapshot_json = VALUES(report_snapshot_json),
                    pdf_path = VALUES(pdf_path),
                    generated_at = VALUES(generated_at)'
            );
            $statement->execute([
                'test_session_id' => $testSessionId,
                'reference' => $report['reference'],
                'primary_area_code' => $primaryAreaCode,
                'score_json' => json_encode($report['scores'], JSON_UNESCAPED_UNICODE),
                'ranking_json' => json_encode($report['rankings'], JSON_UNESCAPED_UNICODE),
                'report_snapshot_json' => json_encode($report, JSON_UNESCAPED_UNICODE),
                'pdf_path' => $pdfPath,
                'generated_at' => $report['generated_at'],
            ]);
        } catch (Throwable) {
        }
    }

    private function updatePdfPath(string $reference, string $pdfPath): void
    {
        try {
            $statement = $this->pdo->prepare('UPDATE reports SET pdf_path = :pdf_path WHERE reference = :reference');
            $statement->execute([
                'pdf_path' => $pdfPath,
                'reference' => $reference,
            ]);
        } catch (Throwable) {
        }
    }

    private function allFromDatabase(): array
    {
        try {
            $statement = $this->pdo->query('SELECT report_snapshot_json FROM reports ORDER BY generated_at DESC');
            $reports = [];

            foreach ($statement->fetchAll() as $row) {
                $report = json_decode((string) $row['report_snapshot_json'], true);

                if (is_array($report)) {
                    $reports[] = $report;
                }
            }

            return $reports;
        } catch (Throwable) {
            return [];
        }
    }

    private function findInDatabase(string $reference): ?array
    {
        try {
            $statement = $this->pdo->prepare('SELECT report_snapshot_json FROM reports WHERE reference = :reference LIMIT 1');
            $statement->execute(['reference' => $reference]);
            $snapshot = $statement->fetchColumn();

            if ($snapshot === false) {
                return null;
            }

            $report = json_decode((string) $snapshot, true);

            return is_array($report) ? $report : null;
        } catch (Throwable) {
            return null;
        }
    }

    private function deleteFromDatabase(string $reference): void
    {
        try {
            $this->pdo->beginTransaction();
            $statement = $this->pdo->prepare('SELECT test_session_id FROM reports WHERE reference = :reference LIMIT 1');
            $statement->execute(['reference' => $reference]);
            $testSessionId = $statement->fetchColumn();

            if ($testSessionId !== false) {
                $deleteSession = $this->pdo->prepare('DELETE FROM test_sessions WHERE id = :id');
                $deleteSession->execute(['id' => (int) $testSessionId]);
            }

            $deleteReport = $this->pdo->prepare('DELETE FROM reports WHERE reference = :reference');
            $deleteReport->execute(['reference' => $reference]);
            $this->pdo->commit();
        } catch (Throwable) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
        }
    }
}