<?php

declare(strict_types=1);

final class MyronSiteAnalyticsRepository
{
    public function __construct(private readonly PDO $pdo)
    {
    }

    /**
     * @param array<string, mixed> $data
     */
    public function recordPageview(array $data): void
    {
        $statement = $this->pdo->prepare(
            'INSERT INTO site_pageviews (
                id, page_path, page_template_id, referrer_domain,
                utm_source, utm_medium, utm_campaign, utm_content, utm_term,
                session_hash, device_type, browser_family, os_family, country_code, viewed_at
             ) VALUES (
                :id, :page_path, :page_template_id, :referrer_domain,
                :utm_source, :utm_medium, :utm_campaign, :utm_content, :utm_term,
                :session_hash, :device_type, :browser_family, :os_family, :country_code, :viewed_at
             )'
        );
        $statement->execute([
            ':id' => myronGenerateId('spv'),
            ':page_path' => $this->clip((string) ($data['page_path'] ?? '/'), 1000),
            ':page_template_id' => trim((string) ($data['page_template_id'] ?? '')) !== '' ? $this->clip((string) $data['page_template_id'], 32) : null,
            ':referrer_domain' => $this->clip((string) ($data['referrer_domain'] ?? ''), 255),
            ':utm_source' => $this->clip((string) ($data['utm_source'] ?? ''), 100),
            ':utm_medium' => $this->clip((string) ($data['utm_medium'] ?? ''), 100),
            ':utm_campaign' => $this->clip((string) ($data['utm_campaign'] ?? ''), 100),
            ':utm_content' => $this->clip((string) ($data['utm_content'] ?? ''), 100),
            ':utm_term' => $this->clip((string) ($data['utm_term'] ?? ''), 100),
            ':session_hash' => $this->clip((string) ($data['session_hash'] ?? ''), 64),
            ':device_type' => $this->clip((string) ($data['device_type'] ?? 'desktop'), 20),
            ':browser_family' => $this->clip((string) ($data['browser_family'] ?? 'Other'), 50),
            ':os_family' => $this->clip((string) ($data['os_family'] ?? 'Other'), 50),
            ':country_code' => strtoupper($this->clip((string) ($data['country_code'] ?? ''), 2)),
            ':viewed_at' => $this->clip((string) ($data['viewed_at'] ?? myronNow()), 40),
        ]);
    }

    public function recordEvent(string $event, string $path, string $sessionHash): void
    {
        $statement = $this->pdo->prepare(
            'INSERT INTO site_analytics_events (id, event_name, page_path, session_hash, recorded_at)
             VALUES (:id, :event_name, :page_path, :session_hash, :recorded_at)'
        );
        $statement->execute([
            ':id' => myronGenerateId('sae'),
            ':event_name' => $this->clip($event, 100),
            ':page_path' => $this->clip($path, 1000),
            ':session_hash' => $this->clip($sessionHash, 64),
            ':recorded_at' => myronNow(),
        ]);
    }

    /**
     * @return array{visits:int,pageviews:int,top_country:string,top_device:string,top_page:string,top_referrer:string}
     */
    public function getSummary(int $days): array
    {
        $cutoff = $this->cutoff($days);
        $counts = $this->pdo->prepare(
            'SELECT COUNT(*) AS pageviews, COUNT(DISTINCT session_hash) AS visits
             FROM site_pageviews
             WHERE viewed_at >= :cutoff'
        );
        $counts->execute([':cutoff' => $cutoff]);
        $row = $counts->fetch(PDO::FETCH_ASSOC) ?: [];

        return [
            'visits' => (int) ($row['visits'] ?? 0),
            'pageviews' => (int) ($row['pageviews'] ?? 0),
            'top_country' => $this->topValue('country_code', $cutoff),
            'top_device' => $this->topValue('device_type', $cutoff),
            'top_page' => (string) (($this->getTopPages($days, 1)[0]['path'] ?? '') ?: ''),
            'top_referrer' => (string) (($this->getTrafficSources($days, 1)[0]['domain'] ?? '') ?: ''),
        ];
    }

    /**
     * @return list<array{path:string,views:int}>
     */
    public function getTopPages(int $days, int $limit = 10): array
    {
        $statement = $this->pdo->prepare(
            'SELECT page_path, COUNT(*) AS views
             FROM site_pageviews
             WHERE viewed_at >= :cutoff
             GROUP BY page_path
             ORDER BY views DESC, page_path ASC
             LIMIT :limit'
        );
        $statement->bindValue(':cutoff', $this->cutoff($days), PDO::PARAM_STR);
        $statement->bindValue(':limit', max(1, min(100, $limit)), PDO::PARAM_INT);
        $statement->execute();
        $rows = $statement->fetchAll(PDO::FETCH_ASSOC) ?: [];
        return array_map(static fn (array $row): array => [
            'path' => (string) ($row['page_path'] ?? ''),
            'views' => (int) ($row['views'] ?? 0),
        ], $rows);
    }

    /**
     * @return list<array{domain:string,visits:int}>
     */
    public function getTrafficSources(int $days, int $limit = 10): array
    {
        $statement = $this->pdo->prepare(
            "SELECT referrer_domain, COUNT(DISTINCT session_hash) AS visits
             FROM site_pageviews
             WHERE viewed_at >= :cutoff AND referrer_domain != ''
             GROUP BY referrer_domain
             ORDER BY visits DESC, referrer_domain ASC
             LIMIT :limit"
        );
        $statement->bindValue(':cutoff', $this->cutoff($days), PDO::PARAM_STR);
        $statement->bindValue(':limit', max(1, min(100, $limit)), PDO::PARAM_INT);
        $statement->execute();
        $rows = $statement->fetchAll(PDO::FETCH_ASSOC) ?: [];
        return array_map(static fn (array $row): array => [
            'domain' => (string) ($row['referrer_domain'] ?? ''),
            'visits' => (int) ($row['visits'] ?? 0),
        ], $rows);
    }

    /**
     * @return list<array{date:string,pageviews:int}>
     */
    public function getDailyPageviews(int $days): array
    {
        $statement = $this->pdo->prepare(
            'SELECT SUBSTR(viewed_at, 1, 10) AS date_key, COUNT(*) AS pageviews
             FROM site_pageviews
             WHERE viewed_at >= :cutoff
             GROUP BY date_key
             ORDER BY date_key ASC'
        );
        $statement->execute([':cutoff' => $this->cutoff($days)]);
        $rows = $statement->fetchAll(PDO::FETCH_ASSOC) ?: [];
        return array_map(static fn (array $row): array => [
            'date' => (string) ($row['date_key'] ?? ''),
            'pageviews' => (int) ($row['pageviews'] ?? 0),
        ], $rows);
    }

    public function purgeExpired(int $days): int
    {
        $cutoff = $this->cutoff($days);
        $pageviews = $this->pdo->prepare('DELETE FROM site_pageviews WHERE viewed_at < :cutoff');
        $pageviews->execute([':cutoff' => $cutoff]);
        $events = $this->pdo->prepare('DELETE FROM site_analytics_events WHERE recorded_at < :cutoff');
        $events->execute([':cutoff' => $cutoff]);
        return $pageviews->rowCount() + $events->rowCount();
    }

    private function topValue(string $column, string $cutoff): string
    {
        if (!in_array($column, ['country_code', 'device_type'], true)) {
            return '';
        }
        $statement = $this->pdo->prepare(
            "SELECT {$column} AS value, COUNT(*) AS count_value
             FROM site_pageviews
             WHERE viewed_at >= :cutoff AND {$column} != ''
             GROUP BY {$column}
             ORDER BY count_value DESC, {$column} ASC
             LIMIT 1"
        );
        $statement->execute([':cutoff' => $cutoff]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);
        return is_array($row) ? (string) ($row['value'] ?? '') : '';
    }

    private function cutoff(int $days): string
    {
        $days = max(1, min(3650, $days));
        return (new DateTimeImmutable('now', new DateTimeZone('UTC')))->modify('-' . $days . ' days')->format('Y-m-d H:i:s');
    }

    private function clip(string $value, int $max): string
    {
        return substr(trim($value), 0, $max);
    }
}
