<?php

namespace App\Services\Meta;

use App\Exceptions\MetaMarketingApiException;
use App\Models\MetaCampaignSafetyAudit;
use App\Services\Customer\CustomerQualityService;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Schema;
use RuntimeException;
use Throwable;

class MetaCampaignSafetyAuditService
{
    public function __construct(
        private readonly CustomerQualityService $customerQualityService
    ) {
    }

    public function audit(): MetaCampaignSafetyAudit
    {
        $this->assertReady();

        $audit = MetaCampaignSafetyAudit::query()->create([
            'status' => 'processing',
            'started_at' => now(),
        ]);

        try {
            $settings = $this->customerQualityService->settings();
            $riskAudience = $this->resolveAudience(
                trim((string) data_get(
                    $settings,
                    'audiences.' . CustomerQualityService::RISK_AUDIENCE_KEY . '.meta_audience_id'
                )),
                $this->customerQualityService->audienceName(CustomerQualityService::RISK_AUDIENCE_KEY)
            );
            $purchaserAudience = $this->resolveAudience(
                trim((string) config('services.meta_marketing.purchaser_audience_id')),
                trim((string) config(
                    'services.meta_marketing.purchaser_audience_name',
                    'Last 180 Day Purchase 10/02/26'
                ))
            );
            $adsets = $this->fetchAdsets();
            $rows = $this->complianceRows($adsets, $riskAudience, $purchaserAudience);

            $audit->fill([
                'status' => 'completed',
                'risk_audience_id' => $riskAudience['id'],
                'risk_audience_name' => $riskAudience['name'],
                'purchaser_audience_id' => $purchaserAudience['id'],
                'purchaser_audience_name' => $purchaserAudience['name'],
                'total_adsets' => $rows->count(),
                'risk_compliant_count' => $rows->where('risk_excluded', true)->count(),
                'risk_missing_count' => $rows->where('risk_excluded', false)->count(),
                'purchaser_excluded_count' => $rows->where('purchaser_excluded', true)->count(),
                'rows' => $rows->all(),
                'finished_at' => now(),
            ])->save();

            return $audit->fresh();
        } catch (Throwable $exception) {
            $context = $exception instanceof MetaMarketingApiException
                ? $exception->logContext()
                : [];

            $audit->fill([
                'status' => 'failed',
                'meta_http_status' => data_get($context, 'http_status'),
                'meta_error_code' => data_get($context, 'code'),
                'meta_error_subcode' => data_get($context, 'subcode'),
                'meta_trace_id' => data_get($context, 'trace_id'),
                'error_message' => $exception->getMessage(),
                'finished_at' => now(),
            ])->save();

            throw $exception;
        }
    }

    public function complianceRows(
        array $adsets,
        array $riskAudience,
        array $purchaserAudience
    ): Collection {
        return collect($adsets)->map(function (array $adset) use ($riskAudience, $purchaserAudience) {
            $excluded = collect((array) data_get($adset, 'targeting.excluded_custom_audiences', []))
                ->map(fn ($audience) => [
                    'id' => trim((string) data_get($audience, 'id')),
                    'name' => trim((string) data_get($audience, 'name')),
                ])
                ->filter(fn (array $audience) => $audience['id'] !== '')
                ->values();
            $excludedIds = $excluded->pluck('id')->all();

            return [
                'campaign_id' => trim((string) data_get($adset, 'campaign_id')),
                'campaign_name' => trim((string) data_get($adset, 'campaign.name', 'Unknown campaign')),
                'campaign_status' => trim((string) data_get($adset, 'campaign.effective_status')),
                'adset_id' => trim((string) data_get($adset, 'id')),
                'adset_name' => trim((string) data_get($adset, 'name', 'Unknown ad set')),
                'adset_status' => trim((string) data_get($adset, 'effective_status')),
                'risk_excluded' => in_array($riskAudience['id'], $excludedIds, true),
                'purchaser_excluded' => in_array($purchaserAudience['id'], $excludedIds, true),
                'excluded_audiences' => $excluded->all(),
            ];
        })->values();
    }

    private function resolveAudience(string $configuredId, string $name): array
    {
        if ($configuredId !== '') {
            $response = Http::timeout($this->timeout())->get(
                $this->graphUrl('/' . $configuredId),
                [
                    'access_token' => $this->accessToken(),
                    'fields' => 'id,name',
                ]
            );

            if ($response->successful() && filled(data_get($response->json(), 'id'))) {
                return [
                    'id' => trim((string) data_get($response->json(), 'id')),
                    'name' => trim((string) data_get($response->json(), 'name', $name)),
                ];
            }
        }

        $audiences = $this->fetchPages(
            $this->graphUrl('/' . $this->adAccountId() . '/customaudiences'),
            [
                'access_token' => $this->accessToken(),
                'fields' => 'id,name,subtype,operation_status',
                'limit' => 500,
            ],
            'Meta custom audience lookup'
        );
        $match = collect($audiences)->first(
            fn (array $audience) => strcasecmp(
                trim((string) data_get($audience, 'name')),
                $name
            ) === 0
        );

        if (! $match || blank(data_get($match, 'id'))) {
            throw new RuntimeException("Meta audience not found: {$name}");
        }

        return [
            'id' => trim((string) data_get($match, 'id')),
            'name' => trim((string) data_get($match, 'name', $name)),
        ];
    }

    private function fetchAdsets(): array
    {
        return $this->fetchPages(
            $this->graphUrl('/' . $this->adAccountId() . '/adsets'),
            [
                'access_token' => $this->accessToken(),
                'fields' => implode(',', [
                    'id',
                    'name',
                    'campaign_id',
                    'status',
                    'effective_status',
                    'targeting',
                    'campaign{id,name,status,effective_status}',
                ]),
                'limit' => 500,
            ],
            'Meta ad set targeting audit'
        );
    }

    private function fetchPages(string $url, array $query, string $operation): array
    {
        $rows = [];

        for ($page = 0; $page < 100 && $url !== ''; $page++) {
            $response = Http::timeout($this->timeout())->get($url, $query);
            $this->assertSuccessful($operation, $response);

            foreach ((array) data_get($response->json(), 'data', []) as $row) {
                if (is_array($row)) {
                    $rows[] = $row;
                }
            }

            $next = trim((string) data_get($response->json(), 'paging.next'));
            $url = str_starts_with($next, 'https://graph.facebook.com/') ? $next : '';
            $query = [];
        }

        return $rows;
    }

    private function assertReady(): void
    {
        if (! Schema::hasTable('meta_campaign_safety_audits')) {
            throw new RuntimeException('Meta campaign safety audit table is missing. Run migrations first.');
        }

        if ($this->accessToken() === '' || $this->adAccountId() === '') {
            throw new RuntimeException('Meta Marketing API access token and Ad Account ID are required.');
        }
    }

    private function assertSuccessful(string $operation, Response $response): void
    {
        if (! $response->successful()) {
            throw MetaMarketingApiException::fromResponse($operation, $response);
        }
    }

    private function graphUrl(string $path): string
    {
        $version = trim((string) config('services.meta_marketing.graph_version', 'v20.0'));
        $version = str_starts_with($version, 'v') ? $version : 'v' . $version;

        return 'https://graph.facebook.com/' . $version . $path;
    }

    private function accessToken(): string
    {
        return trim((string) config('services.meta_marketing.access_token'));
    }

    private function adAccountId(): string
    {
        $id = trim((string) config('services.meta_marketing.ad_account_id'));

        return $id !== '' && ! str_starts_with($id, 'act_') ? 'act_' . $id : $id;
    }

    private function timeout(): int
    {
        return max(5, (int) config('services.meta_marketing.timeout', 15));
    }
}
