<?php

namespace App\Campaigns;

use App\Models\Campaign;
use App\Services\Meta\Meta;
use App\Support\CurrencyMinorUnits;
use Illuminate\Support\Carbon;
use Throwable;

class MetaPerformanceProfileReader
{
    public function __construct(private readonly Meta $meta) {}

    /**
     * @param  array{
     *     platform: string,
     *     external_campaign_id: string,
     *     local_campaign_id: ?int,
     *     local_google_ads_campaign_id: ?int
     * }  $candidate
     * @return array<string, mixed>
     */
    public function read(array $candidate): array
    {
        $local = $candidate['local_campaign_id']
            ? Campaign::query()->find($candidate['local_campaign_id'])
            : Campaign::query()->where('external_campaign_id', $candidate['external_campaign_id'])->first();

        try {
            $campaign = $this->meta->campaign($candidate['external_campaign_id']);
            $adSets = $this->meta->performanceAdSets($candidate['external_campaign_id']);
        } catch (Throwable $exception) {
            return $this->fallback($local, $exception->getMessage());
        }

        try {
            $primaryAdSet = $this->primaryAdSet($adSets);
            $learning = $this->learningFrom($primaryAdSet);
            [$deliveryStatus, $deliveryDetail] = $this->deliveryFrom($campaign, $primaryAdSet, $learning);
            $regions = $this->regionsFrom($primaryAdSet, $local);
            $languages = $this->languagesFrom($primaryAdSet);

            return [
                'name' => trim((string) ($campaign['name'] ?? '')) ?: ($local?->name),
                'objective' => trim((string) ($campaign['objective'] ?? '')) ?: null,
                'daily_budget' => $this->budget($campaign, $primaryAdSet, $local),
                'currency' => is_string($local?->currency) && $local->currency !== '' ? $local->currency : null,
                'bid_strategy' => trim((string) ($campaign['bid_strategy'] ?? $primaryAdSet['bid_strategy'] ?? '')) ?: null,
                'platform_status' => $this->platformStatus($campaign),
                'delivery_status' => $deliveryStatus,
                'delivery_detail' => $deliveryDetail,
                'regions' => $regions,
                'languages' => $languages,
                'platform_learning' => $learning,
                'platform_payload' => [
                    'campaign' => $campaign,
                    'primary_ad_set' => $primaryAdSet,
                ],
                'account_id' => $this->accountId($campaign),
                'started_at' => $this->startedAt($campaign, $local),
                'last_error' => null,
            ];
        } catch (Throwable $exception) {
            return array_merge($this->fallback($local, $exception->getMessage()), [
                'name' => trim((string) ($campaign['name'] ?? '')) ?: ($local?->name),
                'objective' => trim((string) ($campaign['objective'] ?? '')) ?: null,
                'platform_status' => $this->platformStatus($campaign),
                'platform_payload' => [
                    'campaign' => $campaign,
                ],
                'account_id' => $this->accountId($campaign),
                'started_at' => $this->startedAt($campaign, $local),
            ]);
        }
    }

    /**
     * @param  list<array<string, mixed>>  $adSets
     * @return array<string, mixed>|null
     */
    private function primaryAdSet(array $adSets): ?array
    {
        foreach ($adSets as $adSet) {
            $learning = is_array($adSet['learning_stage_info'] ?? null) ? $adSet['learning_stage_info'] : [];
            $status = strtoupper(trim((string) ($learning['status'] ?? '')));

            if ($status === 'LEARNING') {
                return $adSet;
            }
        }

        foreach ($adSets as $adSet) {
            $status = strtoupper(trim((string) ($adSet['effective_status'] ?? $adSet['status'] ?? '')));

            if (in_array($status, ['ACTIVE', 'PAUSED'], true)) {
                return $adSet;
            }
        }

        return $adSets[0] ?? null;
    }

    /**
     * @param  array<string, mixed>|null  $adSet
     * @return array{source: string, status: ?string, detail: ?string}|null
     */
    private function learningFrom(?array $adSet): ?array
    {
        if ($adSet === null) {
            return null;
        }

        $learning = is_array($adSet['learning_stage_info'] ?? null) ? $adSet['learning_stage_info'] : [];
        $status = strtoupper(trim((string) ($learning['status'] ?? '')));

        if ($status === '') {
            return null;
        }

        return [
            'source' => 'meta_adset',
            'status' => $status,
            'detail' => $this->learningDetail($learning),
        ];
    }

    /**
     * @param  array<string, mixed>  $learning
     */
    private function learningDetail(array $learning): ?string
    {
        $windows = $learning['attribution_windows'] ?? null;

        if (is_array($windows)) {
            $labels = array_values(array_filter(array_map(
                fn (mixed $window): string => trim((string) $window),
                $windows,
            )));

            return $labels !== [] ? implode(', ', $labels) : null;
        }

        $detail = trim((string) $windows);

        return $detail !== '' ? $detail : null;
    }

    /**
     * @param  array<string, mixed>  $campaign
     * @param  array<string, mixed>|null  $adSet
     * @param  array{source: string, status: ?string, detail: ?string}|null  $learning
     * @return array{0: ?string, 1: ?string}
     */
    private function deliveryFrom(array $campaign, ?array $adSet, ?array $learning): array
    {
        $learningStatus = strtoupper(trim((string) ($learning['status'] ?? '')));

        if ($learningStatus === 'LEARNING') {
            return ['LEARNING', 'Ad set is in Meta learning phase.'];
        }

        if ($learningStatus === 'FAIL') {
            return ['LIMITED', 'Ad set exited Meta learning without enough signal.'];
        }

        $issue = $this->firstIssue($adSet);

        if ($issue !== null) {
            return ['LIMITED', $issue];
        }

        $effective = strtoupper(trim((string) ($campaign['effective_status'] ?? $campaign['status'] ?? '')));

        if (str_contains($effective, 'DISAPPROVED') || str_contains($effective, 'WITH_ISSUES')) {
            return ['NOT_DELIVERING', $effective];
        }

        if (in_array($effective, ['ACTIVE', 'PAUSED'], true)) {
            return [$learningStatus === 'SUCCESS' ? 'SUCCESS' : 'ACTIVE', null];
        }

        return [$effective !== '' ? $effective : null, null];
    }

    /**
     * @param  array<string, mixed>|null  $adSet
     */
    private function firstIssue(?array $adSet): ?string
    {
        if ($adSet === null) {
            return null;
        }

        $issues = $adSet['issues_info'] ?? [];

        if (! is_array($issues)) {
            return null;
        }

        foreach ($issues as $issue) {
            if (! is_array($issue)) {
                continue;
            }

            $summary = trim((string) ($issue['error_summary'] ?? $issue['error_message'] ?? ''));

            if ($summary !== '') {
                return $summary;
            }
        }

        return null;
    }

    /**
     * @param  array<string, mixed>|null  $adSet
     * @return list<string>
     */
    private function regionsFrom(?array $adSet, ?Campaign $local): array
    {
        $geo = is_array($adSet['targeting']['geo_locations'] ?? null)
            ? $adSet['targeting']['geo_locations']
            : [];

        $regions = [];

        foreach (['countries', 'country_groups', 'regions', 'cities'] as $key) {
            $values = $geo[$key] ?? null;

            if (! is_array($values)) {
                continue;
            }

            foreach ($values as $value) {
                if (is_string($value) && $value !== '') {
                    $regions[] = $value;
                } elseif (is_array($value)) {
                    $label = trim((string) ($value['name'] ?? $value['key'] ?? ''));

                    if ($label !== '') {
                        $regions[] = $label;
                    }
                }
            }
        }

        $regions = array_values(array_unique($regions));

        if ($regions !== []) {
            return $regions;
        }

        $localLocations = is_array($local?->locations) ? $local->locations : [];
        $countries = is_array($localLocations['countries'] ?? null) ? $localLocations['countries'] : [];

        return array_values(array_filter(array_map(
            fn (mixed $country): string => trim((string) $country),
            $countries,
        )));
    }

    /**
     * @param  array<string, mixed>|null  $adSet
     * @return list<string>
     */
    private function languagesFrom(?array $adSet): array
    {
        $locales = $adSet['targeting']['locales'] ?? null;

        if (! is_array($locales)) {
            return [];
        }

        return array_values(array_filter(array_map(
            fn (mixed $locale): string => trim((string) $locale),
            $locales,
        )));
    }

    /**
     * @param  array<string, mixed>  $campaign
     * @param  array<string, mixed>|null  $adSet
     */
    private function budget(array $campaign, ?array $adSet, ?Campaign $local): ?float
    {
        $minor = $campaign['daily_budget']
            ?? $campaign['lifetime_budget']
            ?? $adSet['daily_budget']
            ?? $adSet['lifetime_budget']
            ?? null;

        if ($minor !== null && $minor !== '') {
            // The currency's factor, not a hardcoded 100. Meta reports budgets
            // in minor units and the yen has none, so a JPY campaign profiled
            // at a hundredth of its real budget and every verdict drawn from
            // this number was drawn from the wrong one.
            return round(CurrencyMinorUnits::toMajor($minor, $local?->currency), 2);
        }

        return $local?->budget !== null ? (float) $local->budget : null;
    }

    /**
     * @param  array<string, mixed>  $campaign
     */
    private function platformStatus(array $campaign): ?string
    {
        $effective = trim((string) ($campaign['effective_status'] ?? ''));

        if ($effective !== '') {
            return $effective;
        }

        $status = trim((string) ($campaign['status'] ?? ''));

        return $status !== '' ? $status : null;
    }

    /**
     * @param  array<string, mixed>  $campaign
     */
    private function accountId(array $campaign): ?string
    {
        $id = trim((string) ($campaign['account_id'] ?? ''));

        if ($id === '') {
            return null;
        }

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

    /**
     * @param  array<string, mixed>  $campaign
     */
    private function startedAt(array $campaign, ?Campaign $local): ?string
    {
        foreach (['start_time', 'created_time'] as $field) {
            $value = $campaign[$field] ?? null;

            if (is_string($value) && $value !== '') {
                return Carbon::parse($value)->toDateString();
            }
        }

        return $local?->starts_at?->toDateString() ?? $local?->created_at?->toDateString();
    }

    /**
     * @return array<string, mixed>
     */
    private function fallback(?Campaign $local, string $message): array
    {
        $locations = is_array($local?->locations) ? $local->locations : [];
        $countries = is_array($locations['countries'] ?? null) ? $locations['countries'] : [];

        return [
            'name' => $local?->name,
            'objective' => $local?->objective,
            'daily_budget' => $local?->budget !== null ? (float) $local->budget : null,
            'currency' => is_string($local?->currency) && $local->currency !== '' ? $local->currency : null,
            'bid_strategy' => $local?->bid_strategy,
            'platform_status' => null,
            'delivery_status' => null,
            'delivery_detail' => null,
            'regions' => array_values(array_filter(array_map(
                fn (mixed $country): string => trim((string) $country),
                $countries,
            ))),
            'languages' => [],
            'platform_learning' => null,
            'platform_payload' => null,
            'account_id' => is_string($local?->ad_account_id) && $local->ad_account_id !== '' ? $local->ad_account_id : null,
            'started_at' => $local?->starts_at?->toDateString() ?? $local?->created_at?->toDateString(),
            'last_error' => mb_substr($message, 0, 2000),
        ];
    }
}
