<?php

namespace App\Library\CampaignDecision;

use App\Campaigns\CampaignPerformanceWhaleEnrichment;
use App\Campaigns\PerformanceDay;
use App\Campaigns\PerformanceWindowRollup;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;

class CampaignPerformanceDataAssembler
{
    public function __construct(
        private readonly CampaignDecisionConfigResolver $config,
        private readonly CampaignPerformanceWhaleEnrichment $affiliate,
        private readonly CampaignDataQualityValidator $quality,
    ) {}

    /**
     * @param  array<string, mixed>  $group
     */
    public function assemble(array $group, string $trigger = 'scheduled_hourly'): CampaignPerformanceContext
    {
        $platform = (string) $group['platform'];
        $campaignId = (string) $group['external_campaign_id'];
        $accountId = is_string($group['account_id'] ?? null) ? $group['account_id'] : null;
        $days = $group['days'] instanceof Collection ? $group['days'] : collect();
        $hours = $group['hours'] instanceof Collection ? $group['hours'] : collect();
        $settings = $this->config->forCampaign($platform, $accountId, $campaignId);
        $lagDays = (int) config('campaign-decisions.monitor.internal_reporting_lag_days', 2);
        $outcomeDays = (int) config('campaign-decisions.monitor.primary_outcome_window_days', 7);

        $affiliate = $this->buildAffiliate($platform, $campaignId, $lagDays, $outcomeDays);
        $windows = $this->buildWindows($days, $hours, $affiliate, $lagDays, $outcomeDays);
        $capabilities = $this->quality->capabilities(
            $days,
            $hours,
            $affiliate,
            (bool) ($group['sync_failed'] ?? false),
        );

        return new CampaignPerformanceContext(
            platform: $platform,
            externalCampaignId: $campaignId,
            userId: (int) $group['user_id'],
            accountId: $accountId,
            name: (string) ($group['name'] ?? 'Campaign '.$campaignId),
            dailyBudget: isset($group['daily_budget']) ? (float) $group['daily_budget'] : null,
            platformStatus: isset($group['platform_status']) ? (string) $group['platform_status'] : null,
            deliveryStatus: isset($group['delivery_status']) ? (string) $group['delivery_status'] : null,
            startedAt: isset($group['started_at']) ? (string) $group['started_at'] : null,
            campaignAgeDays: $this->campaignAgeDays($group['started_at'] ?? null, $days, $affiliate),
            syncFailed: (bool) ($group['sync_failed'] ?? false),
            syncError: isset($group['sync_error']) ? (string) $group['sync_error'] : null,
            platformDataThrough: null,
            days: $days,
            hours: $hours,
            affiliate: $affiliate,
            capabilities: $capabilities,
            windows: $windows,
            settings: $settings,
            trigger: $trigger,
        );
    }

    /**
     * @return array<string, mixed>
     */
    private function buildAffiliate(string $platform, string $campaignId, int $lagDays, int $outcomeDays): array
    {
        if (! $this->affiliate->isEnabled()) {
            return ['matched' => false, 'skipped' => 'disabled'];
        }

        $lagged = $this->affiliate->rollupLagged($campaignId, $outcomeDays, $lagDays);
        $matched = ($lagged['days_with_rows'] ?? 0) > 0;

        if (! $matched) {
            $lagged = $this->affiliate->rollupRoiLagged($campaignId, $platform, $outcomeDays, $lagDays);
            $matched = ($lagged['days_with_rows'] ?? 0) > 0;
        }

        if (! $matched) {
            return [
                'matched' => false,
                'campaign_id' => $campaignId,
                'lag_days' => $lagDays,
            ];
        }

        return [
            'matched' => true,
            'campaign_id' => $campaignId,
            'lag_days' => $lagDays,
            'revenue_source' => (string) ($lagged['revenue_source'] ?? 'affiliate'),
            'outcome_window' => $lagged,
            'data_through' => $lagged['to'],
        ];
    }

    /**
     * @param  Collection<int, PerformanceDay>  $days
     * @param  Collection<int, PerformanceHour>  $hours
     * @param  array<string, mixed>  $affiliate
     * @return array<string, array<string, mixed>>
     */
    private function buildWindows(Collection $days, Collection $hours, array $affiliate, int $lagDays, int $outcomeDays): array
    {
        $windows = [];

        foreach ((array) config('campaign-decisions.monitor.hour_windows', [3, 5, 7]) as $length) {
            $key = $length.'h';
            $rollup = PerformanceWindowRollup::fromHours($key, (int) $length, $hours);
            $windows[$key] = array_merge($rollup, CampaignMetricsCalculator::fromTotals(
                (float) $rollup['spend'],
                (int) $rollup['impressions'],
                (int) $rollup['clicks'],
            ));
        }

        foreach ((array) config('campaign-decisions.monitor.day_windows', [1, 3, 7, 14, 30]) as $length) {
            $key = $length.'d';
            $rollup = PerformanceWindowRollup::fromDays($key, (int) $length, $days);
            $windows[$key] = array_merge($rollup, CampaignMetricsCalculator::fromTotals(
                (float) $rollup['spend'],
                (int) $rollup['impressions'],
                (int) $rollup['clicks'],
            ));
        }

        if (($affiliate['matched'] ?? false) === true) {
            $outcome = $affiliate['outcome_window'] ?? [];
            $from = (string) ($outcome['from'] ?? '');
            $to = (string) ($outcome['to'] ?? '');
            $platformSpend = $this->platformSpendBetween($days, $from, $to);
            $spend = ($outcome['revenue_source'] ?? null) === 'roi_dashboard'
                ? (float) ($outcome['spend'] ?? 0)
                : $platformSpend;
            $windows['outcome_'.$outcomeDays.'d'] = array_merge(
                $outcome,
                ['platform_spend' => $spend],
                CampaignMetricsCalculator::fromTotals(
                    $spend,
                    0,
                    (int) ($outcome['clicks'] ?? 0),
                    (float) ($outcome['orders'] ?? 0),
                    (float) ($outcome['net_revenue'] ?? 0),
                ),
            );
        }

        return $windows;
    }

    /**
     * @param  Collection<int, PerformanceDay>  $days
     */
    /**
     * @param  Collection<int, PerformanceDay>  $days
     */
    private function platformSpendBetween(Collection $days, string $from, string $to): float
    {
        if ($from === '' || $to === '') {
            return 0.0;
        }

        return round((float) $days
            ->filter(fn (PerformanceDay $row): bool => $row->stat_date->toDateString() >= $from
                && $row->stat_date->toDateString() <= $to)
            ->sum('spend'), 2);
    }

    /**
     * @param  Collection<int, PerformanceDay>  $days
     */
    /**
     * @param  array<string, mixed>  $affiliate
     */
    private function campaignAgeDays(?string $startedAt, Collection $days, array $affiliate = []): int
    {
        $start = null;

        if (is_string($startedAt) && $startedAt !== '') {
            $start = Carbon::parse($startedAt)->startOfDay();
        } elseif ($days->isNotEmpty()) {
            $first = $days->min('stat_date');
            $start = $first instanceof Carbon ? $first->copy()->startOfDay() : null;
        } elseif (($affiliate['revenue_source'] ?? null) === 'roi_dashboard') {
            $from = $affiliate['outcome_window']['from'] ?? null;
            $start = is_string($from) && $from !== '' ? Carbon::parse($from)->startOfDay() : null;
        }

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

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