<?php

declare(strict_types=1);

namespace App\Services;

use RuntimeException;
use Throwable;

final class AnalyticsService
{
    private string $propertyId;
    private string $credentialsPath;
    private string $cachePath;
    private int $cacheTtl;

    public function __construct()
    {
        $this->propertyId = (string) config('app.analytics.property_id', '');
        $this->credentialsPath = (string) config('app.analytics.credentials_path', '');
        $this->cachePath = (string) config('app.analytics.cache_path', '');
        $this->cacheTtl = max(60, (int) config('app.analytics.cache_ttl', 900));
    }

    public function dashboard(): array
    {
        $cache = $this->readCache();
        if ($cache !== null && time() - (int) ($cache['cached_at'] ?? 0) < $this->cacheTtl) {
            return $cache + ['available' => true, 'stale' => false];
        }

        try {
            $credentials = $this->credentials();
            $token = $this->accessToken($credentials);
            $data = [
                'available' => true,
                'stale' => false,
                'cached_at' => time(),
                'realtime' => $this->realtime($token),
                'summary' => $this->summary($token),
                'all_time_users' => $this->allTimeUsers($token),
                'daily' => $this->daily($token),
                'pages' => $this->pages($token),
                'channels' => $this->channels($token),
            ];
            $this->writeCache($data);
            return $data;
        } catch (Throwable) {
            if ($cache !== null) {
                $cache['available'] = true;
                $cache['stale'] = true;
                return $cache;
            }
            return [
                'available' => false,
                'stale' => false,
                'message' => '방문 통계를 불러오지 못했습니다. 잠시 후 다시 확인해 주세요.',
            ];
        }
    }

    private function credentials(): array
    {
        if ($this->propertyId === '' || !is_file($this->credentialsPath) || !is_readable($this->credentialsPath)) {
            throw new RuntimeException('Analytics configuration is unavailable.');
        }
        $credentials = json_decode((string) file_get_contents($this->credentialsPath), true);
        if (!is_array($credentials) || empty($credentials['client_email']) || empty($credentials['private_key'])) {
            throw new RuntimeException('Analytics credentials are invalid.');
        }
        return $credentials;
    }

    private function accessToken(array $credentials): string
    {
        $now = time();
        $header = $this->base64Url(json_encode(['alg' => 'RS256', 'typ' => 'JWT'], JSON_THROW_ON_ERROR));
        $claims = $this->base64Url(json_encode([
            'iss' => $credentials['client_email'],
            'scope' => 'https://www.googleapis.com/auth/analytics.readonly',
            'aud' => 'https://oauth2.googleapis.com/token',
            'iat' => $now,
            'exp' => $now + 3600,
        ], JSON_THROW_ON_ERROR));
        $unsigned = $header . '.' . $claims;
        $signature = '';
        if (!openssl_sign($unsigned, $signature, (string) $credentials['private_key'], OPENSSL_ALGO_SHA256)) {
            throw new RuntimeException('Analytics token signing failed.');
        }
        $jwt = $unsigned . '.' . $this->base64Url($signature);
        $response = $this->request('https://oauth2.googleapis.com/token', [
            'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion' => $jwt,
        ], true);
        $token = (string) ($response['access_token'] ?? '');
        if ($token === '') {
            throw new RuntimeException('Analytics token was not issued.');
        }
        return $token;
    }

    private function realtime(string $token): int
    {
        $report = $this->report($token, 'runRealtimeReport', [
            'metrics' => [['name' => 'activeUsers']],
        ]);
        return (int) ($report['rows'][0]['metricValues'][0]['value'] ?? 0);
    }

    private function summary(string $token): array
    {
        $report = $this->report($token, 'runReport', [
            'dateRanges' => [['startDate' => '30daysAgo', 'endDate' => 'today']],
            'metrics' => array_map(static fn(string $name): array => ['name' => $name], [
                'activeUsers', 'newUsers', 'screenPageViews', 'sessions',
            ]),
        ]);
        $values = $report['rows'][0]['metricValues'] ?? [];
        return [
            'active_users' => (int) ($values[0]['value'] ?? 0),
            'new_users' => (int) ($values[1]['value'] ?? 0),
            'page_views' => (int) ($values[2]['value'] ?? 0),
            'sessions' => (int) ($values[3]['value'] ?? 0),
        ];
    }

    private function allTimeUsers(string $token): int
    {
        $report = $this->report($token, 'runReport', [
            'dateRanges' => [['startDate' => '2026-08-12', 'endDate' => 'today']],
            'metrics' => [['name' => 'totalUsers']],
        ]);
        return (int) ($report['rows'][0]['metricValues'][0]['value'] ?? 0);
    }

    private function daily(string $token): array
    {
        $report = $this->report($token, 'runReport', [
            'dateRanges' => [['startDate' => '13daysAgo', 'endDate' => 'today']],
            'dimensions' => [['name' => 'date']],
            'metrics' => [['name' => 'activeUsers'], ['name' => 'screenPageViews']],
            'orderBys' => [['dimension' => ['dimensionName' => 'date']]],
        ]);
        return array_map(static function (array $row): array {
            $date = (string) ($row['dimensionValues'][0]['value'] ?? '');
            return [
                'date' => strlen($date) === 8 ? substr($date, 4, 2) . '.' . substr($date, 6, 2) : $date,
                'users' => (int) ($row['metricValues'][0]['value'] ?? 0),
                'views' => (int) ($row['metricValues'][1]['value'] ?? 0),
            ];
        }, $report['rows'] ?? []);
    }

    private function pages(string $token): array
    {
        $report = $this->report($token, 'runReport', [
            'dateRanges' => [['startDate' => '30daysAgo', 'endDate' => 'today']],
            'dimensions' => [['name' => 'pageTitle'], ['name' => 'pagePath']],
            'metrics' => [['name' => 'screenPageViews']],
            'orderBys' => [['metric' => ['metricName' => 'screenPageViews'], 'desc' => true]],
            'limit' => 5,
        ]);
        return array_map(static fn(array $row): array => [
            'title' => (string) ($row['dimensionValues'][0]['value'] ?? '(제목 없음)'),
            'path' => (string) ($row['dimensionValues'][1]['value'] ?? '/'),
            'views' => (int) ($row['metricValues'][0]['value'] ?? 0),
        ], $report['rows'] ?? []);
    }

    private function channels(string $token): array
    {
        $report = $this->report($token, 'runReport', [
            'dateRanges' => [['startDate' => '30daysAgo', 'endDate' => 'today']],
            'dimensions' => [['name' => 'sessionDefaultChannelGroup']],
            'metrics' => [['name' => 'sessions'], ['name' => 'activeUsers']],
            'orderBys' => [['metric' => ['metricName' => 'sessions'], 'desc' => true]],
            'limit' => 5,
        ]);
        return array_map(static fn(array $row): array => [
            'channel' => (string) ($row['dimensionValues'][0]['value'] ?? 'Unassigned'),
            'sessions' => (int) ($row['metricValues'][0]['value'] ?? 0),
            'users' => (int) ($row['metricValues'][1]['value'] ?? 0),
        ], $report['rows'] ?? []);
    }

    private function report(string $token, string $method, array $body): array
    {
        return $this->request(
            'https://analyticsdata.googleapis.com/v1beta/properties/' . rawurlencode($this->propertyId) . ':' . $method,
            $body,
            false,
            $token
        );
    }

    private function request(string $url, array $body, bool $form = false, string $token = ''): array
    {
        $handle = curl_init($url);
        if ($handle === false) {
            throw new RuntimeException('HTTP client initialization failed.');
        }
        $headers = ['Accept: application/json'];
        $payload = $form ? http_build_query($body) : json_encode($body, JSON_THROW_ON_ERROR);
        $headers[] = $form ? 'Content-Type: application/x-www-form-urlencoded' : 'Content-Type: application/json';
        if ($token !== '') {
            $headers[] = 'Authorization: Bearer ' . $token;
        }
        curl_setopt_array($handle, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $payload,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_TIMEOUT => 12,
        ]);
        $raw = curl_exec($handle);
        $status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
        $error = curl_error($handle);
        curl_close($handle);
        if (!is_string($raw) || $status < 200 || $status >= 300) {
            throw new RuntimeException('Analytics API request failed: ' . ($error !== '' ? $error : (string) $status));
        }
        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            throw new RuntimeException('Analytics API response is invalid.');
        }
        return $decoded;
    }

    private function base64Url(string $value): string
    {
        return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
    }

    private function readCache(): ?array
    {
        if ($this->cachePath === '' || !is_file($this->cachePath) || !is_readable($this->cachePath)) {
            return null;
        }
        $cache = json_decode((string) file_get_contents($this->cachePath), true);
        return is_array($cache) ? $cache : null;
    }

    private function writeCache(array $data): void
    {
        $directory = dirname($this->cachePath);
        if (!is_dir($directory) && !mkdir($directory, 0700, true) && !is_dir($directory)) {
            return;
        }
        file_put_contents($this->cachePath, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), LOCK_EX);
        @chmod($this->cachePath, 0600);
    }
}
