<?php

namespace App\Campaigns;

use App\Library\CampaignDecision\CampaignDecisionEngine;
use App\Library\CampaignDecision\CampaignDecisionMapper;
use App\Library\CampaignDecision\CampaignPerformanceDataAssembler;
use App\Models\User;
use Illuminate\Support\Facades\Log;
use Throwable;

class CampaignPerformanceAdopsMonitor
{
    public function __construct(
        private readonly WhaleAdopsReader $reader,
        private readonly CampaignPerformanceMapper $mapper,
        private readonly CampaignDecisionMapper $decisionMapper,
        private readonly CampaignPerformanceVerdictEvaluator $verdicts,
        private readonly CampaignDecisionEngine $decisionEngine,
        private readonly CampaignPerformanceDataAssembler $assembler,
    ) {}

    /**
     * @param  'hourly'|'daily'|'manual'  $runType
     * @param  (callable(string, array<string, mixed>): void)|null  $onProgress
     * @return array{attempted: int, mapped: int, failed: int, skipped: ?string, results: list<array<string, mixed>>}
     */
    public function run(
        ?string $platform = null,
        ?string $campaign = null,
        ?int $limit = null,
        string $runType = 'hourly',
        ?callable $onProgress = null,
    ): array {
        if (! $this->reader->isAvailable()) {
            return ['attempted' => 0, 'mapped' => 0, 'failed' => 0, 'skipped' => 'adops_unavailable', 'results' => []];
        }

        $platform = strtolower($platform ?: 'meta');
        $allowed = array_map(strtolower(...), config('campaign-performance.adops_platforms', ['meta']));

        if (! in_array($platform, $allowed, true)) {
            return ['attempted' => 0, 'mapped' => 0, 'failed' => 0, 'skipped' => 'platform_not_ready:'.$platform, 'results' => []];
        }

        $attempted = 0;
        $mapped = 0;
        $failed = 0;
        $results = [];
        $cap = $limit ?? (int) config('campaign-performance.adops_limit', 0);

        $onProgress?->__invoke('discovering', ['platform' => $platform]);

        $campaigns = $this->reader->campaigns($platform, $campaign, null, $cap > 0 ? $cap : null);
        $total = count($campaigns);

        $onProgress?->__invoke('discovered', ['total' => $total, 'platform' => $platform, 'run_type' => $runType]);

        foreach ($campaigns as $row) {
            $attempted++;
            $campaignId = (string) $row['campaign_id'];
            $name = (string) ($row['name'] !== '' ? $row['name'] : 'Campaign '.$campaignId);

            $onProgress?->__invoke('campaign_start', [
                'index' => $attempted,
                'total' => $total,
                'platform' => $row['platform'],
                'campaign_id' => $campaignId,
                'name' => $name,
            ]);

            try {
                $group = $this->group($row, $runType);
                $outcome = $this->decisionEngineEnabled()
                    ? $this->decisionMapper->mapOne($group, $this->triggerFor($runType))
                    : $this->mapper->mapOne($group, $runType);

                $mapped++;
                $result = [
                    'platform' => $row['platform'],
                    'campaign_id' => $campaignId,
                    'name' => $name,
                    'success' => true,
                    'verdict' => $outcome['verdict'],
                    'verdict_label' => $outcome['verdict_label'],
                    'reason' => $outcome['reason'],
                    'error' => null,
                ];
                $results[] = $result;
                $onProgress?->__invoke('campaign_done', $result);
            } catch (Throwable $exception) {
                $failed++;
                Log::warning('Adops performance monitoring failed', [
                    'platform' => $row['platform'],
                    'external_campaign_id' => $campaignId,
                    'error' => $exception->getMessage(),
                ]);
                $result = [
                    'platform' => $row['platform'],
                    'campaign_id' => $campaignId,
                    'name' => $name,
                    'success' => false,
                    'verdict' => null,
                    'verdict_label' => null,
                    'reason' => null,
                    'error' => $exception->getMessage(),
                ];
                $results[] = $result;
                $onProgress?->__invoke('campaign_done', $result);
            }
        }

        return [
            'attempted' => $attempted,
            'mapped' => $mapped,
            'failed' => $failed,
            'skipped' => null,
            'results' => $results,
        ];
    }

    /**
     * @return array<string, mixed>|null
     */
    public function health(string $platform, string $campaignId): ?array
    {
        if (! $this->reader->isAvailable()) {
            return null;
        }

        $campaigns = $this->reader->campaigns($platform, $campaignId, null, 1);
        $row = $campaigns[0] ?? null;

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

        $group = $this->group($row, 'hourly');

        if ($this->decisionEngineEnabled()) {
            $decision = $this->decisionEngine->evaluate(
                $this->assembler->assemble($group, 'scheduled_hourly'),
            );

            return [
                'campaign' => $row,
                'snapshot' => $decision,
                'evaluation' => [
                    'final_verdict' => $decision['verdict'],
                    'final_reason' => $decision['reason'],
                    'hourly_verdict' => null,
                    'windows' => $decision['windows'] ?? [],
                    'decision_engine' => 'v2',
                ],
                'revenue_included' => (bool) ($decision['capabilities']['internal_revenue_available'] ?? false),
            ];
        }

        $snapshot = $this->mapper->snapshotFor($group);
        $evaluation = $this->verdicts->evaluate($snapshot, $group['days'], $group['hours'], 'hourly');

        return [
            'campaign' => $row,
            'snapshot' => $snapshot,
            'evaluation' => [
                'final_verdict' => $evaluation['final_verdict']->value,
                'final_reason' => $evaluation['final_reason'],
                'hourly_verdict' => $evaluation['hourly_verdict'],
                'windows' => $evaluation['windows'],
            ],
            'revenue_included' => false,
        ];
    }

    private function decisionEngineEnabled(): bool
    {
        return (bool) config('campaign-decisions.engine_enabled', false);
    }

    private function triggerFor(string $runType): string
    {
        return $runType === 'daily' ? 'scheduled_affiliate_refresh' : 'scheduled_hourly';
    }

    /**
     * @param  array{
     *     platform: string,
     *     account_id: ?string,
     *     campaign_id: string,
     *     name: string,
     *     status: ?string,
     *     effective_status: ?string,
     *     daily_budget: ?float,
     *     learning_status: ?string,
     *     started_at: ?string
     * }  $row
     * @param  'hourly'|'daily'|'manual'  $runType
     * @return array<string, mixed>
     */
    public function group(array $row, string $runType): array
    {
        $platform = $row['platform'];
        $campaignId = $row['campaign_id'];
        $sinceHour = now()->copy()->startOfHour()->subHours(PerformanceWindows::maxAnalysisHours() - 1);
        $days = $this->reader->dailyStats($platform, $campaignId, now()->subDays(29)->toDateString(), now()->toDateString());
        $hours = $this->reader->hourlyStats($platform, $campaignId, $sinceHour);
        $dataset = $runType === 'hourly' ? 'hourly' : 'daily';
        $sync = $this->reader->latestIngest($platform, $row['account_id'], $dataset);
        $failed = $this->reader->ingestFailed($sync);

        return [
            'platform' => $platform,
            'external_campaign_id' => $campaignId,
            'user_id' => $this->userId(),
            'local_campaign_id' => null,
            'local_google_ads_campaign_id' => null,
            'days' => $days,
            'hours' => $hours,
            'name' => $row['name'] !== '' ? $row['name'] : 'Campaign '.$campaignId,
            'daily_budget' => $row['daily_budget'],
            'platform_status' => $row['effective_status'] ?? $row['status'],
            'delivery_status' => $row['learning_status'],
            'account_id' => $row['account_id'],
            'started_at' => $row['started_at'],
            'sync_failed' => $failed,
            'sync_error' => $failed ? ($sync['error'] ?? 'Whale ingest reported a failure.') : null,
            'source' => 'adops',
            'campaign_profile' => [
                'name' => $row['name'],
                'platform' => $platform,
                'external_campaign_id' => $campaignId,
                'daily_budget' => $row['daily_budget'],
                'platform_status' => $row['effective_status'] ?? $row['status'],
                'platform_learning_label' => $row['learning_status'],
                'account_id' => $row['account_id'],
                'started_at' => $row['started_at'],
            ],
        ];
    }

    private function userId(): int
    {
        $configured = (int) config('campaign-performance.monitor_user_id', 0);

        if ($configured > 0) {
            return $configured;
        }

        $id = User::query()->orderBy('id')->value('id');

        if ($id === null) {
            throw new \RuntimeException('Adops monitoring needs at least one user to store suggestions.');
        }

        return (int) $id;
    }
}
