<?php

namespace App\Services\Integrations\Preview;

use App\Models\PlatformConnection;
use App\Services\Credentials\CredentialVault;
use App\Services\Integrations\GoogleOAuthService;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class DataPreviewService
{
    public function __construct(
        private readonly CredentialVault $vault,
        private readonly GoogleOAuthService $googleOAuth,
    ) {}

    public function preview(PlatformConnection $connection, string $from, string $to, int $limit = 10): DataPreviewResult
    {
        $started = microtime(true);
        $limit = max(1, min($limit, 50));
        $connection->loadMissing(['client', 'secureCredential']);

        try {
            $payload = $this->vault->payload($connection->secureCredential);
            if (empty($payload) && ! $connection->secureCredential) {
                return DataPreviewResult::failed(
                    'Token kasası bulunamadı. Önce bağlantı bilgilerini kaydedin.',
                    $connection->platform,
                    $from,
                    $to,
                    'secure_credential_missing',
                    $this->baseDiagnostics($connection, $payload),
                    $this->duration($started)
                );
            }

            $result = match ($connection->platform) {
                'google_ads' => $this->previewGoogleAds($connection, $payload, $from, $to, $limit, $started),
                'meta_ads' => $this->previewMetaAds($connection, $payload, $from, $to, $limit, $started),
                'search_console' => $this->previewSearchConsole($connection, $payload, $from, $to, $limit, $started),
                'ga4' => $this->previewGa4($connection, $payload, $from, $to, $limit, $started),
                default => DataPreviewResult::failed('Desteklenmeyen platform.', $connection->platform, $from, $to, 'unsupported_platform', [], $this->duration($started)),
            };

            $this->storeResultOnConnection($connection, $result);

            return $result;
        } catch (\Throwable $e) {
            $result = DataPreviewResult::failed(
                'Veri önizleme başarısız.',
                $connection->platform,
                $from,
                $to,
                $e->getMessage(),
                $this->baseDiagnostics($connection, $this->vault->payload($connection->secureCredential)),
                $this->duration($started)
            );

            $this->storeResultOnConnection($connection, $result);

            return $result;
        }
    }

    private function previewGoogleAds(PlatformConnection $connection, array $payload, string $from, string $to, int $limit, float $started): DataPreviewResult
    {
        $customerId = preg_replace('/\D+/', '', (string) ($payload['customer_id'] ?? $connection->account_id));
        $developerToken = $payload['developer_token'] ?? config('services.google_ads.developer_token', env('GOOGLE_ADS_DEVELOPER_TOKEN'));
        $loginCustomerId = preg_replace('/\D+/', '', (string) ($payload['login_customer_id'] ?? ''));

        if (blank($customerId)) {
            return DataPreviewResult::failed('Google Ads customer_id eksik.', 'google_ads', $from, $to, 'customer_id_missing', $this->baseDiagnostics($connection, $payload), $this->duration($started));
        }
        if (blank($developerToken)) {
            return DataPreviewResult::failed('Google Ads developer token eksik.', 'google_ads', $from, $to, 'developer_token_missing', $this->baseDiagnostics($connection, $payload), $this->duration($started));
        }

        $query = "
            SELECT
                segments.date,
                campaign.id,
                campaign.name,
                campaign.status,
                metrics.impressions,
                metrics.clicks,
                metrics.cost_micros,
                metrics.conversions,
                metrics.ctr,
                metrics.average_cpc
            FROM campaign
            WHERE segments.date BETWEEN '{$from}' AND '{$to}'
            ORDER BY metrics.cost_micros DESC
            LIMIT {$limit}
        ";

        $rows = $this->googleAdsSearchStream($connection, $customerId, $developerToken, $loginCustomerId, $query);
        $normalized = collect($rows)->map(fn (array $row) => [
            'date' => Arr::get($row, 'segments.date'),
            'id' => (string) Arr::get($row, 'campaign.id'),
            'name' => Arr::get($row, 'campaign.name'),
            'status' => Arr::get($row, 'campaign.status'),
            'impressions' => (int) Arr::get($row, 'metrics.impressions', 0),
            'clicks' => (int) Arr::get($row, 'metrics.clicks', 0),
            'cost' => $this->microsToMoney(Arr::get($row, 'metrics.costMicros', Arr::get($row, 'metrics.cost_micros', 0))),
            'conversions' => (float) Arr::get($row, 'metrics.conversions', 0),
            'ctr' => (float) Arr::get($row, 'metrics.ctr', 0),
            'average_cpc' => $this->microsToMoney(Arr::get($row, 'metrics.averageCpc', Arr::get($row, 'metrics.average_cpc', 0))),
        ])->values()->all();

        return DataPreviewResult::success(
            count($normalized) ? 'Google Ads kampanya verisi alındı.' : 'Google Ads bağlantısı çalıştı ancak seçili aralıkta kampanya verisi dönmedi.',
            'google_ads',
            $from,
            $to,
            $this->totals($normalized, ['cost', 'clicks', 'impressions', 'conversions']),
            $normalized,
            array_merge($this->baseDiagnostics($connection, $payload), [
                'query_type' => 'campaign_preview',
                'customer_id' => $this->mask($customerId),
                'api_version' => config('services.google_ads.version', env('GOOGLE_ADS_API_VERSION', 'v23')),
                'row_count' => count($normalized),
            ]),
            $this->duration($started)
        );
    }

    private function previewMetaAds(PlatformConnection $connection, array $payload, string $from, string $to, int $limit, float $started): DataPreviewResult
    {
        $token = $payload['access_token'] ?? null;
        $account = $payload['ad_account_id'] ?? $payload['account_id'] ?? $connection->account_id;
        $account = Str::startsWith((string) $account, 'act_') ? (string) $account : 'act_'.preg_replace('/\D+/', '', (string) $account);

        if (blank($token)) {
            return DataPreviewResult::failed('Meta access_token eksik.', 'meta_ads', $from, $to, 'access_token_missing', $this->baseDiagnostics($connection, $payload), $this->duration($started));
        }
        if (blank($account) || $account === 'act_') {
            return DataPreviewResult::failed('Meta ad_account_id eksik.', 'meta_ads', $from, $to, 'ad_account_id_missing', $this->baseDiagnostics($connection, $payload), $this->duration($started));
        }

        $version = config('services.meta.graph_version', env('META_GRAPH_VERSION', 'v23.0'));
        $endpoint = "https://graph.facebook.com/{$version}/{$account}/insights";
        $response = Http::timeout(60)->get($endpoint, [
            'fields' => 'campaign_id,campaign_name,spend,impressions,reach,clicks,inline_link_clicks,cpc,cpm,ctr,actions,date_start,date_stop',
            'level' => 'campaign',
            'time_increment' => 1,
            'time_range' => json_encode(['since' => $from, 'until' => $to]),
            'limit' => $limit,
            'access_token' => $token,
        ]);

        if (! $response->successful()) {
            throw new \RuntimeException('Meta Insights önizleme sorgusu başarısız: '.Str::limit($response->body(), 1000));
        }

        $rows = collect($response->json('data') ?? [])->map(fn (array $row) => [
            'date' => $row['date_start'] ?? $from,
            'id' => $row['campaign_id'] ?? null,
            'name' => $row['campaign_name'] ?? null,
            'spend' => (float) ($row['spend'] ?? 0),
            'impressions' => (int) ($row['impressions'] ?? 0),
            'reach' => (int) ($row['reach'] ?? 0),
            'clicks' => (int) ($row['clicks'] ?? 0),
            'inline_link_clicks' => (int) ($row['inline_link_clicks'] ?? 0),
            'ctr' => (float) ($row['ctr'] ?? 0),
            'cpc' => (float) ($row['cpc'] ?? 0),
            'cpm' => (float) ($row['cpm'] ?? 0),
            'leads' => $this->sumActions($row['actions'] ?? [], ['lead', 'onsite_conversion.lead_grouped', 'offsite_conversion.fb_pixel_lead']),
            'purchases' => $this->sumActions($row['actions'] ?? [], ['purchase', 'offsite_conversion.fb_pixel_purchase', 'omni_purchase']),
        ])->values()->all();

        return DataPreviewResult::success(
            count($rows) ? 'Meta kampanya verisi alındı.' : 'Meta bağlantısı çalıştı ancak seçili aralıkta kampanya verisi dönmedi.',
            'meta_ads',
            $from,
            $to,
            $this->totals($rows, ['spend', 'clicks', 'impressions', 'reach', 'leads', 'purchases']),
            $rows,
            array_merge($this->baseDiagnostics($connection, $payload), [
                'query_type' => 'campaign_insights_preview',
                'ad_account_id' => $this->mask($account),
                'graph_version' => $version,
                'row_count' => count($rows),
            ]),
            $this->duration($started)
        );
    }

    private function previewSearchConsole(PlatformConnection $connection, array $payload, string $from, string $to, int $limit, float $started): DataPreviewResult
    {
        $siteUrl = $payload['site_url'] ?? $connection->account_id ?? $connection->account_name;
        if (blank($siteUrl)) {
            return DataPreviewResult::failed('Search Console site_url eksik.', 'search_console', $from, $to, 'site_url_missing', $this->baseDiagnostics($connection, $payload), $this->duration($started));
        }

        $endpoint = 'https://www.googleapis.com/webmasters/v3/sites/'.rawurlencode((string) $siteUrl).'/searchAnalytics/query';
        $response = Http::withToken($this->googleOAuth->accessTokenFromRefreshToken($connection))->timeout(60)->post($endpoint, [
            'startDate' => $from,
            'endDate' => $to,
            'dimensions' => ['query'],
            'rowLimit' => $limit,
            'startRow' => 0,
        ]);

        if (! $response->successful()) {
            throw new \RuntimeException('Search Console önizleme sorgusu başarısız: '.Str::limit($response->body(), 1000));
        }

        $rows = collect($response->json('rows') ?? [])->map(fn (array $row) => [
            'query' => $row['keys'][0] ?? null,
            'clicks' => (int) ($row['clicks'] ?? 0),
            'impressions' => (int) ($row['impressions'] ?? 0),
            'ctr' => (float) ($row['ctr'] ?? 0),
            'position' => (float) ($row['position'] ?? 0),
        ])->values()->all();

        return DataPreviewResult::success(
            count($rows) ? 'Search Console sorgu verisi alındı.' : 'Search Console bağlantısı çalıştı ancak seçili aralıkta sorgu verisi dönmedi.',
            'search_console',
            $from,
            $to,
            $this->totals($rows, ['clicks', 'impressions']),
            $rows,
            array_merge($this->baseDiagnostics($connection, $payload), [
                'query_type' => 'search_analytics_query_preview',
                'site_url' => $siteUrl,
                'row_count' => count($rows),
            ]),
            $this->duration($started)
        );
    }

    private function previewGa4(PlatformConnection $connection, array $payload, string $from, string $to, int $limit, float $started): DataPreviewResult
    {
        $propertyId = preg_replace('/\D+/', '', (string) ($payload['property_id'] ?? $connection->account_id));
        if (blank($propertyId)) {
            return DataPreviewResult::failed('GA4 property_id eksik.', 'ga4', $from, $to, 'property_id_missing', $this->baseDiagnostics($connection, $payload), $this->duration($started));
        }

        $endpoint = "https://analyticsdata.googleapis.com/v1beta/properties/{$propertyId}:runReport";
        $response = Http::withToken($this->googleOAuth->accessTokenFromRefreshToken($connection))->timeout(60)->post($endpoint, [
            'dateRanges' => [[
                'startDate' => $from,
                'endDate' => $to,
            ]],
            'dimensions' => [['name' => 'sessionDefaultChannelGroup']],
            'metrics' => [
                ['name' => 'sessions'],
                ['name' => 'totalUsers'],
                ['name' => 'screenPageViews'],
                ['name' => 'conversions'],
                ['name' => 'totalRevenue'],
            ],
            'limit' => $limit,
        ]);

        if (! $response->successful()) {
            throw new \RuntimeException('GA4 önizleme sorgusu başarısız: '.Str::limit($response->body(), 1000));
        }

        $rows = collect($response->json('rows') ?? [])->map(function (array $row) {
            $metrics = $row['metricValues'] ?? [];
            return [
                'channel' => $row['dimensionValues'][0]['value'] ?? '(not set)',
                'sessions' => (int) ($metrics[0]['value'] ?? 0),
                'total_users' => (int) ($metrics[1]['value'] ?? 0),
                'screen_page_views' => (int) ($metrics[2]['value'] ?? 0),
                'conversions' => (float) ($metrics[3]['value'] ?? 0),
                'total_revenue' => (float) ($metrics[4]['value'] ?? 0),
            ];
        })->values()->all();

        return DataPreviewResult::success(
            count($rows) ? 'GA4 kanal verisi alındı.' : 'GA4 bağlantısı çalıştı ancak seçili aralıkta trafik verisi dönmedi.',
            'ga4',
            $from,
            $to,
            $this->totals($rows, ['sessions', 'total_users', 'screen_page_views', 'conversions', 'total_revenue']),
            $rows,
            array_merge($this->baseDiagnostics($connection, $payload), [
                'query_type' => 'ga4_channel_preview',
                'property_id' => $this->mask($propertyId),
                'row_count' => count($rows),
            ]),
            $this->duration($started)
        );
    }

    private function googleAdsSearchStream(PlatformConnection $connection, string $customerId, string $developerToken, ?string $loginCustomerId, string $query): array
    {
        $version = config('services.google_ads.version', env('GOOGLE_ADS_API_VERSION', 'v23'));
        $headers = [
            'Authorization' => 'Bearer '.$this->googleOAuth->accessTokenFromRefreshToken($connection),
            'developer-token' => $developerToken,
        ];
        if (filled($loginCustomerId)) {
            $headers['login-customer-id'] = $loginCustomerId;
        }

        $endpoint = "https://googleads.googleapis.com/{$version}/customers/{$customerId}/googleAds:searchStream";
        $response = Http::withHeaders($headers)->timeout(60)->post($endpoint, ['query' => trim($query)]);

        if (! $response->successful()) {
            throw new \RuntimeException('Google Ads önizleme sorgusu başarısız: '.Str::limit($response->body(), 1000));
        }

        $chunks = $response->json();
        $results = [];
        foreach (is_array($chunks) ? $chunks : [] as $chunk) {
            foreach (($chunk['results'] ?? []) as $result) {
                $results[] = $result;
            }
        }

        return $results;
    }

    private function totals(array $rows, array $keys): array
    {
        $totals = [];
        foreach ($keys as $key) {
            $totals[$key] = round((float) collect($rows)->sum($key), 4);
        }

        if (($totals['impressions'] ?? 0) > 0 && isset($totals['clicks'])) {
            $totals['ctr'] = round(($totals['clicks'] / max(1, $totals['impressions'])) * 100, 4);
        }
        if (($totals['clicks'] ?? 0) > 0 && isset($totals['cost'])) {
            $totals['average_cpc'] = round($totals['cost'] / max(1, $totals['clicks']), 4);
        }
        if (($totals['clicks'] ?? 0) > 0 && isset($totals['spend'])) {
            $totals['cpc'] = round($totals['spend'] / max(1, $totals['clicks']), 4);
        }

        return $totals;
    }

    private function baseDiagnostics(PlatformConnection $connection, array $payload): array
    {
        return [
            'connection_id' => $connection->id,
            'client_id' => $connection->client_id,
            'connection_name' => $connection->connection_name,
            'account_id' => $connection->account_id ? $this->mask((string) $connection->account_id) : null,
            'credential_id' => $connection->secure_credential_id,
            'credential_payload_keys' => array_values(array_keys($payload)),
            'has_secure_credential' => (bool) $connection->secure_credential_id,
        ];
    }

    private function storeResultOnConnection(PlatformConnection $connection, DataPreviewResult $result): void
    {
        $meta = $connection->meta ?? [];
        $meta['last_preview_result'] = $result->toArray();

        $connection->forceFill([
            'last_error' => $result->ok ? null : $result->error,
            'meta' => $meta,
        ])->save();
    }

    private function microsToMoney(mixed $value): float
    {
        return round(((float) $value) / 1000000, 4);
    }

    private function sumActions(array $actions, array $needles): float
    {
        $sum = 0.0;
        foreach ($actions as $action) {
            $type = (string) ($action['action_type'] ?? '');
            foreach ($needles as $needle) {
                if ($type === $needle || str_contains($type, $needle)) {
                    $sum += (float) ($action['value'] ?? 0);
                    break;
                }
            }
        }

        return $sum;
    }

    private function mask(string $value): string
    {
        if ($value === '') {
            return '';
        }
        if (strlen($value) <= 8) {
            return str_repeat('•', strlen($value));
        }

        return substr($value, 0, 4).'••••'.substr($value, -4);
    }

    private function duration(float $started): int
    {
        return (int) round((microtime(true) - $started) * 1000);
    }
}
