<?php

namespace App\Campaigns;

use App\Models\CampaignPerformanceSuggestion;
use App\Models\CampaignPerformanceVerdictLog;
use App\Services\AI\CampaignPerformanceAdvisor;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

class CampaignPerformanceMapper
{
    public function __construct(
        private readonly CampaignPerformanceAdvisor $advisor,
        private readonly CampaignPerformanceVerdictEvaluator $verdicts,
        private readonly CampaignPerformanceWhaleEnrichment $whale,
        private readonly PerformanceWhaleVerdictOverlay $whaleVerdict,
    ) {}

    /**
     * Map one already-loaded Whale campaign group.
     *
     * @param  array<string, mixed>  $group
     * @param  'hourly'|'daily'|'manual'  $runType
     */
    /**
     * @return array{verdict: string, verdict_label: string, reason: string}
     */
    public function mapOne(array $group, string $runType = 'daily'): array
    {
        return $this->mapGroup($group, $runType);
    }

    /**
     * @param  array<string, mixed>  $group
     * @return array<string, mixed>
     */
    public function snapshotFor(array $group): array
    {
        return $this->snapshot($group);
    }

    /**
     * @param  array{
     *     platform: string,
     *     external_campaign_id: string,
     *     user_id: int,
     *     local_campaign_id: ?int,
     *     local_google_ads_campaign_id: ?int,
     *     days: Collection<int, PerformanceDay>,
     *     hours: Collection<int, PerformanceHour>
     * }  $group
     * @param  'hourly'|'daily'|'manual'  $runType
     * @return array{verdict: string, verdict_label: string, reason: string}
     */
    private function mapGroup(array $group, string $runType = 'daily'): array
    {
        $snapshot = $this->snapshot($group);
        $snapshot = $this->whale->enrichSnapshot($snapshot, $group['external_campaign_id']);
        $evaluation = $this->verdicts->evaluate($snapshot, $group['days'], $group['hours'], $runType);

        [$finalVerdict, $finalReason] = $this->whaleVerdict->apply(
            $snapshot,
            $evaluation['final_verdict'],
            $evaluation['final_reason'],
        );
        $evaluation['final_verdict'] = $finalVerdict;
        $evaluation['final_reason'] = $finalReason;
        $snapshot['verdicts'] = [
            'run_type' => $runType,
            'windows' => $evaluation['windows'],
            'hourly_verdict' => $evaluation['hourly_verdict'],
            'final_verdict' => $evaluation['final_verdict']->value,
            'final_reason' => $evaluation['final_reason'],
        ];
        $snapshot['allowed_verdicts'] = array_map(
            fn (PerformanceVerdict $verdict): string => $verdict->value,
            PerformanceVerdict::cases(),
        );

        $this->appendVerdictLog($group, $snapshot, $evaluation, $runType);

        $snapshot['final_verdict'] = $evaluation['final_verdict']->value;
        $snapshot['final_reason'] = $evaluation['final_reason'];

        $php = new PerformanceAdvice(
            status: $evaluation['final_verdict'],
            reason: $evaluation['final_reason'],
            source: 'php',
        );
        $advice = $this->advisor->explain($snapshot, $php);

        $row = CampaignPerformanceSuggestion::query()
            ->where('platform', $group['platform'])
            ->where('external_campaign_id', $group['external_campaign_id'])
            ->first() ?? new CampaignPerformanceSuggestion;

        $row->fill([
            'user_id' => $group['user_id'],
            'platform' => $group['platform'],
            'external_campaign_id' => $group['external_campaign_id'],
            'local_campaign_id' => $group['local_campaign_id'],
            'local_google_ads_campaign_id' => $group['local_google_ads_campaign_id'],
            'status' => $advice->status->value,
            'source' => $advice->source,
            'reason' => $advice->reason,
            'confidence' => $advice->confidence,
            'action_state' => 'pending',
            'snapshot' => $snapshot,
            'model' => $advice->model,
            'last_error' => $advice->lastError,
            'suggested_at' => now(),
        ]);
        $row->save();

        return [
            'verdict' => $evaluation['final_verdict']->value,
            'verdict_label' => $evaluation['final_verdict']->label(),
            'reason' => $evaluation['final_reason'],
        ];
    }

    /**
     * @param  array{
     *     platform: string,
     *     external_campaign_id: string,
     *     user_id: int,
     *     local_campaign_id: ?int,
     *     local_google_ads_campaign_id: ?int
     * }  $group
     * @param  array<string, mixed>  $snapshot
     * @param  array{
     *     windows: array<string, array<string, mixed>>,
     *     hourly_verdict: ?array{verdict: string, reason: string},
     *     final_verdict: PerformanceVerdict,
     *     final_reason: string
     * }  $evaluation
     * @param  'hourly'|'daily'|'manual'  $runType
     */
    private function appendVerdictLog(array $group, array $snapshot, array $evaluation, string $runType): void
    {
        CampaignPerformanceVerdictLog::query()->create([
            'user_id' => $group['user_id'],
            'platform' => $group['platform'],
            'external_campaign_id' => $group['external_campaign_id'],
            'local_campaign_id' => $group['local_campaign_id'],
            'local_google_ads_campaign_id' => $group['local_google_ads_campaign_id'],
            'evaluated_at' => now(),
            'run_type' => $runType,
            'final_verdict' => $evaluation['final_verdict']->value,
            'final_reason' => $evaluation['final_reason'],
            'platform_status' => $snapshot['platform_status'] ?? null,
            'campaign' => is_array($snapshot['campaign_profile'] ?? null)
                ? $snapshot['campaign_profile']
                : [
                    'name' => $snapshot['campaign'] ?? null,
                    'platform' => $group['platform'],
                    'external_campaign_id' => $group['external_campaign_id'],
                ],
            'windows' => $evaluation['windows'],
            'hourly_verdict' => $evaluation['hourly_verdict'],
            'source' => 'php',
        ]);
    }

    /**
     * @param  array{
     *     platform: string,
     *     external_campaign_id: string,
     *     user_id: int,
     *     local_campaign_id: ?int,
     *     local_google_ads_campaign_id: ?int,
     *     days: Collection<int, PerformanceDay>,
     *     hours: Collection<int, PerformanceHour>
     * }  $group
     * @return array<string, mixed>
     */
    private function snapshot(array $group): array
    {
        $yesterday = $group['days']->first(
            fn (PerformanceDay $row): bool => $row->stat_date->toDateString() === now()->subDay()->toDateString(),
        );
        $latestDay = $group['days']->sortByDesc(
            fn (PerformanceDay $row): int => $row->stat_date->timestamp,
        )->first();
        $latestHour = $group['hours']->sortByDesc(
            fn (PerformanceHour $row): int => $row->stat_hour->timestamp,
        )->first();
        $latest = $latestHour ?? $latestDay;

        $firstDate = $group['days']->min('stat_date') ?? $group['hours']->min('stat_hour');
        $daysLive = $this->daysLive($firstDate, $group);
        $hourTotals = $this->hourTotals($group['hours']);
        $syncFailed = (bool) ($group['sync_failed'] ?? false);
        [$name, $budget] = $this->campaignMeta($group);
        $metrics = PerformanceTrend::fromDays($group['days']);

        return [
            'campaign' => $name,
            'platform' => $group['platform'],
            'platform_status' => $group['platform_status'] ?? $latest?->platform_status,
            'delivery_status' => $group['delivery_status'] ?? $latestDay?->delivery_status,
            'daily_budget' => $budget,
            'campaign_profile' => is_array($group['campaign_profile'] ?? null)
                ? $group['campaign_profile']
                : null,
            'data_source' => 'whale_adops',
            'revenue_included' => false,
            'days_live' => $daysLive,
            'learning_window_days' => (int) config('campaign-performance.learning_window_days', 3),
            'max_test_spend' => (float) config('campaign-performance.max_test_spend', 40),
            'last_7_days' => $metrics['last_7_days'],
            'previous_7_days' => $metrics['previous_7_days'],
            'last_7_hours' => $hourTotals,
            'lifetime' => $metrics['lifetime'],
            'trend' => $metrics['trend'],
            'trend_summary' => $metrics['trend_summary'],
            'trend_cpc_change_pct' => $metrics['trend_cpc_change_pct'],
            'trend_clicks_change_pct' => $metrics['trend_clicks_change_pct'],
            'yesterday' => [
                'spend' => round((float) ($yesterday?->spend ?? 0), 2),
                'impressions' => (int) ($yesterday?->impressions ?? 0),
                'clicks' => (int) ($yesterday?->clicks ?? 0),
            ],
            'rules' => [
                'zero_click_spend_floor' => (float) config('campaign-performance.zero_click_spend_floor', 10),
                'max_cpc' => (float) config('campaign-performance.max_cpc', 2),
            ],
            'sync_failed' => $syncFailed,
            'sync_error' => $group['sync_error'] ?? $latest?->last_error,
        ];
    }

    /**
     * @param  Collection<int, PerformanceHour>  $hours
     * @return array{spend: float, impressions: int, clicks: int, cpc: ?float, ctr: ?float, hours: int}
     */
    private function hourTotals(Collection $hours): array
    {
        $spend = round((float) $hours->sum('spend'), 2);
        $impressions = (int) $hours->sum('impressions');
        $clicks = (int) $hours->sum('clicks');

        return [
            'spend' => $spend,
            'impressions' => $impressions,
            'clicks' => $clicks,
            'cpc' => $clicks > 0 ? round($spend / $clicks, 4) : null,
            'ctr' => $impressions > 0 ? round($clicks / $impressions, 4) : null,
            'hours' => $hours->count(),
        ];
    }

    /**
     * @param  array{
     *     platform: string,
     *     local_campaign_id: ?int,
     *     local_google_ads_campaign_id: ?int,
     *     days: Collection<int, PerformanceDay>
     * }  $group
     */
    private function daysLive(mixed $firstDate, array $group): int
    {
        $start = $firstDate instanceof Carbon
            ? $firstDate
            : (is_string($firstDate) && $firstDate !== '' ? Carbon::parse($firstDate) : null);

        if ($start === null && is_string($group['started_at'] ?? null) && $group['started_at'] !== '') {
            $start = Carbon::parse($group['started_at']);
        }

        if ($start === null) {
            return 0;
        }

        return (int) $start->startOfDay()->diffInDays(now()->startOfDay()) + 1;
    }

    /**
     * @param  array{
     *     platform: string,
     *     external_campaign_id: string,
     *     local_campaign_id: ?int,
     *     local_google_ads_campaign_id: ?int
     * }  $group
     * @return array{0: string, 1: ?float}
     */
    private function campaignMeta(array $group): array
    {
        $name = is_string($group['name'] ?? null) ? trim((string) $group['name']) : '';

        return [
            $name !== '' ? $name : 'Campaign '.$group['external_campaign_id'],
            array_key_exists('daily_budget', $group)
                ? (isset($group['daily_budget']) ? (float) $group['daily_budget'] : null)
                : null,
        ];
    }
}
