<?php

namespace App\Services\Sync;

use App\Models\GoogleCampaignSnapshot;
use App\Models\GoogleKeywordSnapshot;
use App\Models\GoogleSearchTermSnapshot;
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 GoogleAdsSyncService
{
    public function __construct(
        private readonly CredentialVault $vault,
        private readonly GoogleOAuthService $oauth,
    ) {}

    public function sync(PlatformConnection $connection, string $from, string $to): SyncResult
    {
        $started = microtime(true);

        try {
            $payload = $this->vault->payload($connection->secureCredential);
            $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 SyncResult::failed('Google Ads customer_id eksik. Bağlantı formunda hesap ID girilmeli.');
            }

            if (blank($developerToken)) {
                return SyncResult::failed('Google Ads developer token eksik.');
            }

            $campaigns = $this->syncCampaigns($connection, $customerId, $developerToken, $loginCustomerId, $from, $to);
            $keywords = $this->syncKeywords($connection, $customerId, $developerToken, $loginCustomerId, $from, $to);
            $terms = $this->syncSearchTerms($connection, $customerId, $developerToken, $loginCustomerId, $from, $to);

            $duration = (int) round((microtime(true) - $started) * 1000);

            return SyncResult::success('Google Ads verileri senkronize edildi.', [
                'campaigns' => $campaigns,
                'keywords' => $keywords,
                'search_terms' => $terms,
            ], $duration);
        } catch (\Throwable $e) {
            $duration = (int) round((microtime(true) - $started) * 1000);

            return SyncResult::failed('Google Ads senkronizasyonu başarısız.', $e->getMessage(), [], $duration);
        }
    }

    private function syncCampaigns(PlatformConnection $connection, string $customerId, string $developerToken, ?string $loginCustomerId, string $from, string $to): int
    {
        $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}'
        ";

        $rows = $this->searchStream($connection, $customerId, $developerToken, $loginCustomerId, $query);
        $count = 0;

        foreach ($rows as $row) {
            $date = (string) Arr::get($row, 'segments.date');
            $campaignId = (string) Arr::get($row, 'campaign.id');
            if (blank($date) || blank($campaignId)) {
                continue;
            }

            GoogleCampaignSnapshot::updateOrCreate(
                ['platform_connection_id' => $connection->id, 'date' => $date, 'campaign_id' => $campaignId],
                [
                    'client_id' => $connection->client_id,
                    'campaign_name' => Arr::get($row, 'campaign.name'),
                    'campaign_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))),
                    'raw_json' => json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                ]
            );
            $count++;
        }

        return $count;
    }

    private function syncKeywords(PlatformConnection $connection, string $customerId, string $developerToken, ?string $loginCustomerId, string $from, string $to): int
    {
        $query = "
            SELECT
                segments.date,
                campaign.id,
                campaign.name,
                ad_group.id,
                ad_group.name,
                ad_group_criterion.criterion_id,
                ad_group_criterion.keyword.text,
                ad_group_criterion.keyword.match_type,
                metrics.impressions,
                metrics.clicks,
                metrics.cost_micros,
                metrics.conversions,
                metrics.ctr,
                metrics.average_cpc
            FROM keyword_view
            WHERE segments.date BETWEEN '{$from}' AND '{$to}'
        ";

        $rows = $this->searchStream($connection, $customerId, $developerToken, $loginCustomerId, $query);
        $count = 0;

        foreach ($rows as $row) {
            $date = (string) Arr::get($row, 'segments.date');
            $criterionId = (string) Arr::get($row, 'adGroupCriterion.criterionId', Arr::get($row, 'ad_group_criterion.criterion_id'));
            $keywordText = (string) Arr::get($row, 'adGroupCriterion.keyword.text', Arr::get($row, 'ad_group_criterion.keyword.text'));
            if (blank($date) || (blank($criterionId) && blank($keywordText))) {
                continue;
            }
            $rowHash = sha1(implode('|', [$date, Arr::get($row, 'campaign.id'), Arr::get($row, 'adGroup.id'), $criterionId, $keywordText]));

            GoogleKeywordSnapshot::updateOrCreate(
                ['platform_connection_id' => $connection->id, 'date' => $date, 'row_hash' => $rowHash],
                [
                    'client_id' => $connection->client_id,
                    'campaign_id' => Arr::get($row, 'campaign.id'),
                    'campaign_name' => Arr::get($row, 'campaign.name'),
                    'ad_group_id' => Arr::get($row, 'adGroup.id', Arr::get($row, 'ad_group.id')),
                    'ad_group_name' => Arr::get($row, 'adGroup.name', Arr::get($row, 'ad_group.name')),
                    'criterion_id' => $criterionId,
                    'keyword_text' => $keywordText,
                    'match_type' => Arr::get($row, 'adGroupCriterion.keyword.matchType', Arr::get($row, 'ad_group_criterion.keyword.match_type')),
                    '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))),
                    'raw_json' => json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                ]
            );
            $count++;
        }

        return $count;
    }

    private function syncSearchTerms(PlatformConnection $connection, string $customerId, string $developerToken, ?string $loginCustomerId, string $from, string $to): int
    {
        $query = "
            SELECT
                segments.date,
                campaign.id,
                campaign.name,
                ad_group.id,
                ad_group.name,
                search_term_view.search_term,
                search_term_view.status,
                metrics.impressions,
                metrics.clicks,
                metrics.cost_micros,
                metrics.conversions,
                metrics.ctr,
                metrics.average_cpc
            FROM search_term_view
            WHERE segments.date BETWEEN '{$from}' AND '{$to}'
        ";

        $rows = $this->searchStream($connection, $customerId, $developerToken, $loginCustomerId, $query);
        $count = 0;

        foreach ($rows as $row) {
            $date = (string) Arr::get($row, 'segments.date');
            $term = (string) Arr::get($row, 'searchTermView.searchTerm', Arr::get($row, 'search_term_view.search_term'));
            if (blank($date) || blank($term)) {
                continue;
            }
            $rowHash = sha1(implode('|', [$date, Arr::get($row, 'campaign.id'), Arr::get($row, 'adGroup.id'), $term]));

            GoogleSearchTermSnapshot::updateOrCreate(
                ['platform_connection_id' => $connection->id, 'date' => $date, 'row_hash' => $rowHash],
                [
                    'client_id' => $connection->client_id,
                    'campaign_id' => Arr::get($row, 'campaign.id'),
                    'campaign_name' => Arr::get($row, 'campaign.name'),
                    'ad_group_id' => Arr::get($row, 'adGroup.id', Arr::get($row, 'ad_group.id')),
                    'ad_group_name' => Arr::get($row, 'adGroup.name', Arr::get($row, 'ad_group.name')),
                    'search_term' => $term,
                    'match_type' => Arr::get($row, 'searchTermView.status', Arr::get($row, 'search_term_view.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))),
                    'raw_json' => json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
                ]
            );
            $count++;
        }

        return $count;
    }

    private function searchStream(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->oauth->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(90)->post($endpoint, ['query' => trim($query)]);

        if (! $response->successful()) {
            throw new \RuntimeException('Google Ads 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 microsToMoney(mixed $value): float
    {
        if (! is_numeric($value)) {
            return 0.0;
        }

        return ((float) $value) / 1000000;
    }
}
