<?php

namespace App\Services\Reports;

use App\Models\CustomReport;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class ReportExecutor
{
    public function __construct(protected ReportCompiler $compiler) {}

    public function preview(array $ir): array
    {
        $q = $this->compiler->compile($ir);
        $q->limit(config('reports.preview_limit'));
        return $this->withTimeout(fn () => $q->get()->map(fn ($r) => (array) $r)->all());
    }

    public function execute(CustomReport $report): array
    {
        $ttl = (int) config('reports.cache_ttl_seconds', 0);
        $cacheKey = $this->cacheKey($report);

        if ($ttl > 0 && ($cached = Cache::get($cacheKey)) !== null) {
            return $cached;
        }

        $q = $this->compiler->compile($report->ir);
        $rows = $this->withTimeout(fn () => $q->get()->map(fn ($r) => (array) $r)->all());

        // Only update timestamp + last_compiled_sql when we actually executed
        $report->forceFill([
            'last_executed_at' => now(),
            'last_compiled_sql' => $q->toSql(),
        ])->saveQuietly();

        if ($ttl > 0) {
            Cache::put($cacheKey, $rows, $ttl);
        }

        return $rows;
    }

    protected function cacheKey(CustomReport $report): string
    {
        return 'reports:exec:'.$report->id.':'.$report->updated_at?->getTimestamp();
    }

    public function exportRows(CustomReport $report, int $maxRows): \Generator
    {
        $q = $this->compiler->compile($report->ir);
        $q->limit(min($maxRows, config('reports.max_export_rows')));
        foreach ($q->cursor() as $row) {
            yield (array) $row;
        }
    }

    protected function withTimeout(\Closure $fn)
    {
        $timeout = (int) config('reports.query_timeout_seconds');
        $isMariaDb = $this->isMariaDb();

        try {
            if ($isMariaDb) {
                // MariaDB: max_statement_time uses SECONDS (not ms), session-scoped
                DB::statement('SET SESSION max_statement_time = '.$timeout);
            } else {
                // MySQL 5.7.4+: MAX_EXECUTION_TIME in milliseconds
                DB::statement('SET SESSION MAX_EXECUTION_TIME = '.($timeout * 1000));
            }
        } catch (\Throwable $e) {
            // If even the variable doesn't exist on this server, log + continue
            Log::warning('ReportExecutor: query timeout var not supported', [
                'mariadb' => $isMariaDb,
                'error' => $e->getMessage(),
            ]);
        }

        try {
            return $fn();
        } finally {
            try {
                if ($isMariaDb) {
                    DB::statement('SET SESSION max_statement_time = 0');
                } else {
                    DB::statement('SET SESSION MAX_EXECUTION_TIME = 0');
                }
            } catch (\Throwable $e) {
                // ignore
            }
        }
    }

    protected function isMariaDb(): bool
    {
        try {
            $version = (string) DB::connection()->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
            return stripos($version, 'MariaDB') !== false;
        } catch (\Throwable $e) {
            return false;
        }
    }
}
