<?php

namespace App\Campaigns;

use App\Models\Campaign;
use App\Models\CampaignPerformanceHourStat;
use App\Models\CampaignPerformanceProfile;
use App\Models\CampaignPerformanceStat;
use App\Models\CampaignPerformanceSuggestion;
use App\Models\CampaignPerformanceVerdictLog;
use App\Models\GoogleAdsCampaign;
use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\LengthAwarePaginator as Paginator;
use Illuminate\Support\Collection;

class PerformanceBoard
{
    /**
     * @param  array{q?: string, platform?: string, status?: string, per_page?: int}  $filters
     * @return array{rows: LengthAwarePaginator, stats: array<string, int>, totals: array<string, float|int|null>}
     */
    public function forUser(User $user, array $filters): array
    {
        $rows = $this->rowsFor($user);
        $stats = [
            'all' => $rows->count(),
            'none' => $rows->where('status', null)->count(),
        ];

        foreach (PerformanceVerdict::cases() as $verdict) {
            $stats[$verdict->value] = $rows->where('status', $verdict->value)->count();
        }

        $totals = [
            'spend_7d' => round((float) $rows->sum('spend_7d'), 2),
            'clicks_7d' => (int) $rows->sum('clicks_7d'),
            'impressions_7d' => (int) $rows->sum('impressions_7d'),
        ];
        $totals['cpc_7d'] = $totals['clicks_7d'] > 0
            ? round($totals['spend_7d'] / $totals['clicks_7d'], 4)
            : null;

        $q = trim((string) ($filters['q'] ?? ''));
        $platform = strtolower(trim((string) ($filters['platform'] ?? '')));
        $status = trim((string) ($filters['status'] ?? ''));

        if ($q !== '') {
            $rows = $rows->filter(function (array $row) use ($q): bool {
                return str_contains(strtolower($row['name']), strtolower($q))
                    || str_contains((string) $row['external_campaign_id'], $q);
            });
        }

        if (in_array($platform, ['meta', 'google'], true)) {
            $rows = $rows->where('platform', $platform);
        }

        if ($status === 'none') {
            $rows = $rows->where('status', null);
        } elseif (PerformanceVerdict::tryFromStored($status) !== null) {
            $rows = $rows->where('status', $status);
        }

        $perPage = in_array((int) ($filters['per_page'] ?? 10), [10, 25, 50], true)
            ? (int) $filters['per_page']
            : 10;
        $page = max(1, (int) request()->query('page', 1));
        $rows = $rows->values();

        $paginator = new Paginator(
            $rows->forPage($page, $perPage)->values(),
            $rows->count(),
            $perPage,
            $page,
            ['path' => request()->url(), 'query' => request()->query()],
        );

        return ['rows' => $paginator, 'stats' => $stats, 'totals' => $totals];
    }

    /**
     * @param  array{
     *     per_page?: int,
     *     log_run_type?: string,
     *     log_verdict?: string,
     *     log_days?: int,
     *     log_per_page?: int
     * }  $filters
     * @return array<string, mixed>|null
     */
    public function detailFor(User $user, string $platform, string $externalId, array $filters = []): ?array
    {
        $platform = strtolower($platform);
        $row = $this->rowsFor($user)->first(
            fn (array $candidate): bool => $candidate['platform'] === $platform
                && $candidate['external_campaign_id'] === $externalId,
        );

        if ($row === null) {
            return null;
        }

        $historyQuery = CampaignPerformanceStat::query()
            ->where('user_id', $user->id)
            ->where('platform', $platform)
            ->where('external_campaign_id', $externalId);

        $chartDays = (clone $historyQuery)
            ->orderBy('stat_date')
            ->orderBy('id')
            ->get();

        $perPage = in_array((int) ($filters['per_page'] ?? 10), [10, 25, 50], true)
            ? (int) $filters['per_page']
            : 10;

        $history = (clone $historyQuery)
            ->orderByDesc('stat_date')
            ->orderByDesc('id')
            ->paginate($perPage)
            ->withQueryString();

        $hourlyByDate = $this->hourlyStatsForHistoryPage($user, $platform, $externalId, $history);

        $lifetimeSpend = round((float) $chartDays->sum('spend'), 2);
        $lifetimeClicks = (int) $chartDays->sum('clicks');
        $lifetimeImpressions = (int) $chartDays->sum('impressions');

        $suggestion = CampaignPerformanceSuggestion::query()
            ->where('user_id', $user->id)
            ->where('platform', $platform)
            ->where('external_campaign_id', $externalId)
            ->first();

        $verdictLogs = $this->verdictLogsFor($user, $platform, $externalId, $filters);

        $latestVerdict = CampaignPerformanceVerdictLog::query()
            ->where('user_id', $user->id)
            ->where('platform', $platform)
            ->where('external_campaign_id', $externalId)
            ->orderByDesc('evaluated_at')
            ->orderByDesc('id')
            ->first();

        $profile = CampaignPerformanceProfile::query()
            ->where('user_id', $user->id)
            ->where('platform', $platform)
            ->where('external_campaign_id', $externalId)
            ->first();

        $local = $platform === 'google'
            ? GoogleAdsCampaign::query()
                ->where('user_id', $user->id)
                ->where('google_campaign_id', $externalId)
                ->first()
            : Campaign::query()
                ->where('user_id', $user->id)
                ->where('external_campaign_id', $externalId)
                ->first();

        $latestHour = CampaignPerformanceHourStat::query()
            ->where('user_id', $user->id)
            ->where('platform', $platform)
            ->where('external_campaign_id', $externalId)
            ->orderByDesc('stat_hour')
            ->orderByDesc('id')
            ->first();

        $suggestionWindows = data_get($suggestion?->snapshot, 'verdicts.windows', []);
        $suggestionWindows = is_array($suggestionWindows) ? $suggestionWindows : [];
        $suggestionHourlyVerdict = data_get($suggestion?->snapshot, 'verdicts.hourly_verdict');
        $suggestionRunType = (string) data_get($suggestion?->snapshot, 'verdicts.run_type', '');

        $affiliate = data_get($suggestion?->snapshot, 'affiliate');
        $affiliate = is_array($affiliate) && ($affiliate['matched'] ?? false) ? $affiliate : null;
        $whale = app(CampaignPerformanceWhaleEnrichment::class);
        if ($affiliate === null && $whale->isEnabled()) {
            $liveAffiliate = data_get($whale->enrichSnapshot([], $externalId), 'affiliate');
            $affiliate = is_array($liveAffiliate) && ($liveAffiliate['matched'] ?? false)
                ? $liveAffiliate
                : null;
        }
        $affiliateDaily = $whale->isEnabled() ? $whale->dailyRows($externalId) : [];
        $affiliateDailyByDate = collect($affiliateDaily)->keyBy('date');

        $platformMetrics7d = PerformanceWindowRollup::fromDays('7d', 7, $chartDays);
        $suggestionWindows = $this->mergePlatformMetricsIntoWindows($suggestionWindows, $chartDays);

        return [
            'row' => $row,
            'history' => $history,
            'hourly_by_date' => $hourlyByDate,
            'chart' => PerformanceChart::fromDays($chartDays),
            'suggestion' => $suggestion,
            'suggestion_windows' => $suggestionWindows,
            'suggestion_hourly_verdict' => is_array($suggestionHourlyVerdict) ? $suggestionHourlyVerdict : null,
            'suggestion_run_type' => $suggestionRunType,
            'latest_hour' => $latestHour,
            'profile' => $profile,
            'verdict_logs' => $verdictLogs,
            'latest_verdict' => $latestVerdict,
            'log_filters' => [
                'run_type' => trim((string) ($filters['log_run_type'] ?? '')),
                'verdict' => trim((string) ($filters['log_verdict'] ?? '')),
                'days' => (int) ($filters['log_days'] ?? 0),
                'per_page' => $verdictLogs->perPage(),
            ],
            'local' => $local,
            'lifetime' => [
                'spend' => $lifetimeSpend,
                'clicks' => $lifetimeClicks,
                'impressions' => $lifetimeImpressions,
                'cpc' => $lifetimeClicks > 0 ? round($lifetimeSpend / $lifetimeClicks, 4) : null,
            ],
            'daily_budget' => $profile?->daily_budget
                ?? ($local instanceof GoogleAdsCampaign
                    ? $local->daily_budget
                    : ($local instanceof Campaign ? $local->budget : data_get($suggestion?->snapshot, 'daily_budget'))),
            'local_status' => $local?->status,
            'affiliate' => $affiliate,
            'affiliate_daily' => $affiliateDaily,
            'affiliate_daily_by_date' => $affiliateDailyByDate,
            'platform_metrics_7d' => [
                'spend' => $platformMetrics7d['spend'],
                'clicks' => $platformMetrics7d['clicks'],
                'cpc' => $platformMetrics7d['cpc'],
                'conversions' => $platformMetrics7d['conversions'],
                'conversion_value' => $platformMetrics7d['conversion_value'],
            ],
        ];
    }

    /**
     * @param  array<string, array<string, mixed>>  $windows
     * @return array<string, array<string, mixed>>
     */
    private function mergePlatformMetricsIntoWindows(array $windows, Collection $days): array
    {
        foreach (['3d' => 3, '7d' => 7] as $windowKey => $length) {
            if (! isset($windows[$windowKey])) {
                continue;
            }

            $rollup = PerformanceWindowRollup::fromDays($windowKey, $length, $days);
            $windows[$windowKey]['conversions'] = $rollup['conversions'];
            $windows[$windowKey]['conversion_value'] = $rollup['conversion_value'];
        }

        return $windows;
    }

    /**
     * @return Collection<int, array<string, mixed>>
     */
    private function rowsFor(User $user): Collection
    {
        $since = now()->subDays(6)->toDateString();
        $identities = $this->identities($user);
        $suggestions = CampaignPerformanceSuggestion::query()
            ->where('user_id', $user->id)
            ->get()
            ->keyBy(fn (CampaignPerformanceSuggestion $row): string => $row->platform.'|'.$row->external_campaign_id);
        $profiles = CampaignPerformanceProfile::query()
            ->where('user_id', $user->id)
            ->get()
            ->keyBy(fn (CampaignPerformanceProfile $row): string => $row->platform.'|'.$row->external_campaign_id);
        $window = CampaignPerformanceStat::query()
            ->where('user_id', $user->id)
            ->whereDate('stat_date', '>=', $since)
            ->get()
            ->groupBy(fn (CampaignPerformanceStat $row): string => $row->platform.'|'.$row->external_campaign_id);
        $latest = CampaignPerformanceStat::query()
            ->where('user_id', $user->id)
            ->orderByDesc('stat_date')
            ->orderByDesc('id')
            ->get()
            ->unique(fn (CampaignPerformanceStat $row): string => $row->platform.'|'.$row->external_campaign_id)
            ->keyBy(fn (CampaignPerformanceStat $row): string => $row->platform.'|'.$row->external_campaign_id);

        $learningWindow = (int) config('campaign-performance.learning_window_days', 3);
        $maxTest = (float) config('campaign-performance.max_test_spend', 40);

        return $identities->map(function (array $identity) use ($suggestions, $profiles, $window, $latest, $learningWindow, $maxTest): array {
            $key = $identity['platform'].'|'.$identity['external_campaign_id'];
            $days = $window->get($key, collect());
            $spend = round((float) $days->sum('spend'), 2);
            $clicks = (int) $days->sum('clicks');
            $platformConversions = (int) $days->sum(fn (CampaignPerformanceStat $row): int => (int) ($row->conversions ?? 0));
            $platformConversionValue = round((float) $days->sum(fn (CampaignPerformanceStat $row): float => (float) ($row->conversion_value ?? 0)), 2);
            $suggestion = $suggestions->get($key);
            $profile = $profiles->get($key);
            $stat = $latest->get($key);
            $daysLive = max(1, (int) ($suggestion?->snapshot['days_live'] ?? 1));
            $lifetimeSpend = (float) data_get($suggestion?->snapshot, 'lifetime.spend', $spend);
            $impressions = (int) $days->sum('impressions');
            $name = $this->resolveCampaignName(
                $identity['external_campaign_id'],
                (string) $identity['name'],
                $profile,
                $suggestion,
            );

            return [
                'name' => $name,
                'platform' => $identity['platform'],
                'external_campaign_id' => $identity['external_campaign_id'],
                'platform_status' => $profile?->platform_status ?? $stat?->platform_status,
                'delivery_status' => $profile?->delivery_status,
                'region_label' => $profile?->regionLabel(),
                'platform_learning_label' => $profile?->platformLearningLabel(),
                'status' => $suggestion?->status,
                'verdict' => PerformanceVerdict::tryFromStored($suggestion?->status)?->value,
                'verdict_label' => PerformanceVerdict::tryFromStored($suggestion?->status)?->label(),
                'action_summary' => PerformanceVerdict::tryFromStored($suggestion?->status)?->actionSummary(),
                'learning' => $this->learningLabel($daysLive, $learningWindow, $lifetimeSpend, $maxTest),
                'spend_7d' => $spend,
                'clicks_7d' => $clicks,
                'impressions_7d' => $impressions,
                'cpc_7d' => $clicks > 0 ? round($spend / $clicks, 4) : null,
                'platform_conversions_7d' => $platformConversions,
                'platform_conversion_value_7d' => $platformConversionValue,
                'affiliate_clicks_7d' => data_get($suggestion?->snapshot, 'affiliate.7d.clicks'),
                'affiliate_sum_cpc_7d' => data_get($suggestion?->snapshot, 'affiliate.7d.sum_cpc'),
                'affiliate_orders_7d' => data_get($suggestion?->snapshot, 'affiliate.7d.orders'),
                'affiliate_matched' => (bool) data_get($suggestion?->snapshot, 'affiliate.matched', false),
                'trend' => data_get($suggestion?->snapshot, 'trend'),
                'trend_summary' => data_get($suggestion?->snapshot, 'trend_summary'),
                'reason' => $suggestion?->reason,
                'affiliate_net_revenue_7d' => data_get($suggestion?->snapshot, 'affiliate.7d.net_revenue'),
                'updated_at' => $suggestion?->suggested_at ?? $stat?->fetched_at,
                'last_error' => $stat?->last_error,
            ];
        })->sortBy('name')->values();
    }

    /**
     * @return Collection<int, array{platform: string, external_campaign_id: string, name: string}>
     */
    private function identities(User $user): Collection
    {
        $rows = collect();

        Campaign::query()
            ->where('user_id', $user->id)
            ->where('platform', 'meta')
            ->whereNotNull('external_campaign_id')
            ->where('external_campaign_id', '!=', '')
            ->get()
            ->each(function (Campaign $campaign) use ($rows): void {
                $rows->put('meta|'.$campaign->external_campaign_id, [
                    'platform' => 'meta',
                    'external_campaign_id' => (string) $campaign->external_campaign_id,
                    'name' => (string) $campaign->name,
                ]);
            });

        GoogleAdsCampaign::query()
            ->where('user_id', $user->id)
            ->whereNotNull('google_campaign_id')
            ->where('google_campaign_id', '!=', '')
            ->get()
            ->each(function (GoogleAdsCampaign $campaign) use ($rows): void {
                $rows->put('google|'.$campaign->google_campaign_id, [
                    'platform' => 'google',
                    'external_campaign_id' => (string) $campaign->google_campaign_id,
                    'name' => (string) $campaign->name,
                ]);
            });

        CampaignPerformanceStat::query()
            ->where('user_id', $user->id)
            ->select('platform', 'external_campaign_id')
            ->distinct()
            ->get()
            ->each(function (CampaignPerformanceStat $stat) use ($rows): void {
                $this->putIdentity(
                    $rows,
                    (string) $stat->platform,
                    (string) $stat->external_campaign_id,
                    'Campaign '.$stat->external_campaign_id,
                );
            });

        CampaignPerformanceHourStat::query()
            ->where('user_id', $user->id)
            ->select('platform', 'external_campaign_id')
            ->distinct()
            ->get()
            ->each(function (CampaignPerformanceHourStat $stat) use ($rows): void {
                $this->putIdentity(
                    $rows,
                    (string) $stat->platform,
                    (string) $stat->external_campaign_id,
                    'Campaign '.$stat->external_campaign_id,
                );
            });

        CampaignPerformanceSuggestion::query()
            ->where('user_id', $user->id)
            ->select('platform', 'external_campaign_id')
            ->distinct()
            ->get()
            ->each(function (CampaignPerformanceSuggestion $suggestion) use ($rows): void {
                $this->putIdentity(
                    $rows,
                    (string) $suggestion->platform,
                    (string) $suggestion->external_campaign_id,
                    'Campaign '.$suggestion->external_campaign_id,
                );
            });

        CampaignPerformanceProfile::query()
            ->where('user_id', $user->id)
            ->select('platform', 'external_campaign_id', 'name')
            ->distinct()
            ->get()
            ->each(function (CampaignPerformanceProfile $profile) use ($rows): void {
                $this->putIdentity(
                    $rows,
                    (string) $profile->platform,
                    (string) $profile->external_campaign_id,
                    filled($profile->name) ? (string) $profile->name : 'Campaign '.$profile->external_campaign_id,
                );
            });

        return $rows->values();
    }

    /**
     * @param  Collection<string, array{platform: string, external_campaign_id: string, name: string}>  $rows
     */
    private function putIdentity(Collection $rows, string $platform, string $externalId, string $name): void
    {
        $key = $platform.'|'.$externalId;

        if ($rows->has($key)) {
            return;
        }

        $rows->put($key, [
            'platform' => $platform,
            'external_campaign_id' => $externalId,
            'name' => $name,
        ]);
    }

    private function resolveCampaignName(
        string $externalId,
        string $identityName,
        ?CampaignPerformanceProfile $profile,
        ?CampaignPerformanceSuggestion $suggestion,
    ): string {
        $candidates = [
            $profile?->name,
            data_get($profile?->platform_payload, 'campaign.name'),
            data_get($suggestion?->snapshot, 'campaign'),
            data_get($suggestion?->snapshot, 'campaign_profile.name'),
            $identityName,
        ];

        foreach ($candidates as $candidate) {
            $name = trim((string) ($candidate ?? ''));

            if ($name !== '' && ! $this->isPlaceholderCampaignName($name, $externalId)) {
                return $name;
            }
        }

        return 'Campaign '.$externalId;
    }

    private function isPlaceholderCampaignName(string $name, string $externalId): bool
    {
        return $name === 'Campaign '.$externalId;
    }

    private function learningLabel(int $daysLive, int $window, float $spend, float $maxTest): string
    {
        $spendText = '$'.number_format($spend, 2);
        $capText = '$'.number_format($maxTest, 2);

        if ($daysLive < $window) {
            return sprintf('Learning day %d of %d · %s of %s test spend', $daysLive, $window, $spendText, $capText);
        }

        return sprintf('%d days live · %s spend vs %s test cap', $daysLive, $spendText, $capText);
    }

    /**
     * @param  array{
     *     log_run_type?: string,
     *     log_verdict?: string,
     *     log_days?: int,
     *     log_per_page?: int
     * }  $filters
     */
    private function verdictLogsFor(User $user, string $platform, string $externalId, array $filters): LengthAwarePaginator
    {
        $query = CampaignPerformanceVerdictLog::query()
            ->where('user_id', $user->id)
            ->where('platform', $platform)
            ->where('external_campaign_id', $externalId);

        $runType = trim((string) ($filters['log_run_type'] ?? ''));

        if (in_array($runType, ['hourly', 'daily', 'manual'], true)) {
            $query->where('run_type', $runType);
        }

        $verdict = trim((string) ($filters['log_verdict'] ?? ''));

        if (PerformanceVerdict::tryFromStored($verdict) !== null) {
            $query->where('final_verdict', $verdict);
        }

        $days = (int) ($filters['log_days'] ?? 0);

        if (in_array($days, [7, 30, 90], true)) {
            $query->where('evaluated_at', '>=', now()->subDays($days));
        }

        $perPage = in_array((int) ($filters['log_per_page'] ?? 10), [10, 25, 50], true)
            ? (int) $filters['log_per_page']
            : 10;

        return $query
            ->orderByDesc('evaluated_at')
            ->orderByDesc('id')
            ->paginate($perPage, ['*'], 'log_page')
            ->withQueryString();
    }

    /**
     * @return Collection<string, Collection<int, CampaignPerformanceHourStat>>
     */
    private function hourlyStatsForHistoryPage(
        User $user,
        string $platform,
        string $externalId,
        LengthAwarePaginator $history,
    ): Collection {
        $dates = $history->getCollection()
            ->pluck('stat_date')
            ->filter()
            ->map(fn ($date): string => $date->toDateString())
            ->unique()
            ->values();

        if ($dates->isEmpty()) {
            return collect();
        }

        return CampaignPerformanceHourStat::query()
            ->where('user_id', $user->id)
            ->where('platform', $platform)
            ->where('external_campaign_id', $externalId)
            ->whereDate('stat_hour', '>=', $dates->min())
            ->whereDate('stat_hour', '<=', $dates->max())
            ->orderBy('stat_hour')
            ->get()
            ->groupBy(fn (CampaignPerformanceHourStat $row): string => $row->stat_hour->toDateString());
    }
}
