<?php

namespace App\Campaigns;

use App\Models\GoogleAdsCampaign;
use App\Services\GoogleAdsService;
use Carbon\CarbonInterface;
use Illuminate\Support\Carbon;
use RuntimeException;

class GooglePerformanceReader
{
    public function __construct(private readonly GoogleAdsService $googleAds) {}

    public function startedOn(GoogleAdsCampaign $campaign): ?string
    {
        $connection = $campaign->connection;

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

        try {
            return $this->googleAds->readCampaignStartDate(
                $connection,
                (string) $campaign->google_campaign_id,
            );
        } catch (RuntimeException) {
            return null;
        }
    }

    /**
     * @return list<array{
     *     stat_date: string,
     *     spend: float,
     *     impressions: int,
     *     clicks: int,
     *     ctr: ?float,
     *     cpc: ?float,
     *     platform_status: ?string,
     *     delivery_status: ?string,
     *     platform_payload: array<string, mixed>
     * }>
     */
    public function days(GoogleAdsCampaign $campaign, string $since, string $until): array
    {
        $connection = $campaign->connection;

        if ($connection === null) {
            throw new RuntimeException('Google Ads connection is missing for this campaign.');
        }

        $rows = $this->googleAds->readCampaignMetrics(
            $connection,
            (string) $campaign->google_campaign_id,
            $since,
            $until,
        );

        return array_values(array_filter(array_map(
            fn (array $row): ?array => $this->mapDay($row),
            $rows,
        )));
    }

    /**
     * @return list<array{
     *     stat_hour: string,
     *     spend: float,
     *     impressions: int,
     *     clicks: int,
     *     ctr: ?float,
     *     cpc: ?float,
     *     platform_status: ?string,
     *     delivery_status: ?string,
     *     platform_payload: array<string, mixed>
     * }>
     */
    public function hours(GoogleAdsCampaign $campaign, CarbonInterface $since, CarbonInterface $until): array
    {
        $connection = $campaign->connection;

        if ($connection === null) {
            throw new RuntimeException('Google Ads connection is missing for this campaign.');
        }

        $rows = $this->googleAds->readCampaignHourlyMetrics(
            $connection,
            (string) $campaign->google_campaign_id,
            $since->toDateString(),
            $until->toDateString(),
        );

        return array_values(array_filter(array_map(
            fn (array $row): ?array => $this->mapHour($row, $since, $until),
            $rows,
        )));
    }

    /**
     * @param  array<string, mixed>  $row
     * @return array{
     *     stat_date: string,
     *     spend: float,
     *     impressions: int,
     *     clicks: int,
     *     ctr: ?float,
     *     cpc: ?float,
     *     platform_status: ?string,
     *     delivery_status: ?string,
     *     platform_payload: array<string, mixed>
     * }|null
     */
    private function mapDay(array $row): ?array
    {
        $date = (string) data_get($row, 'segments.date', '');

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

        $impressions = max(0, (int) data_get($row, 'metrics.impressions', 0));
        $clicks = max(0, (int) data_get($row, 'metrics.clicks', 0));
        $costMicros = (int) (data_get($row, 'metrics.costMicros') ?? data_get($row, 'metrics.cost_micros', 0));
        $spend = round($costMicros / 1_000_000, 2);

        $averageCpcMicros = data_get($row, 'metrics.averageCpc') ?? data_get($row, 'metrics.average_cpc');
        $cpc = $clicks > 0
            ? round($spend / $clicks, 4)
            : (is_numeric($averageCpcMicros) ? round(((float) $averageCpcMicros) / 1_000_000, 4) : null);

        $status = data_get($row, 'campaign.status');

        return [
            'stat_date' => $date,
            'spend' => $spend,
            'impressions' => $impressions,
            'clicks' => $clicks,
            'ctr' => $impressions > 0 ? round($clicks / $impressions, 4) : $this->optionalFloat(data_get($row, 'metrics.ctr')),
            'cpc' => $cpc,
            'platform_status' => is_string($status) && $status !== '' ? $status : null,
            'delivery_status' => null,
            'platform_payload' => $row,
        ];
    }

    /**
     * @param  array<string, mixed>  $row
     * @return array{
     *     stat_hour: string,
     *     spend: float,
     *     impressions: int,
     *     clicks: int,
     *     ctr: ?float,
     *     cpc: ?float,
     *     platform_status: ?string,
     *     delivery_status: ?string,
     *     platform_payload: array<string, mixed>
     * }|null
     */
    private function mapHour(array $row, CarbonInterface $since, CarbonInterface $until): ?array
    {
        $date = (string) (data_get($row, 'segments.date') ?? '');
        $hourOfDay = data_get($row, 'segments.hour');

        if ($date === '' || ! is_numeric($hourOfDay)) {
            return null;
        }

        $hour = Carbon::parse($date)->startOfDay()->addHours((int) $hourOfDay);

        if ($hour->lt($since->copy()->startOfHour()) || $hour->gt($until->copy()->startOfHour())) {
            return null;
        }

        $impressions = max(0, (int) data_get($row, 'metrics.impressions', 0));
        $clicks = max(0, (int) data_get($row, 'metrics.clicks', 0));
        $costMicros = (int) (data_get($row, 'metrics.costMicros') ?? data_get($row, 'metrics.cost_micros', 0));
        $spend = round($costMicros / 1_000_000, 2);

        $averageCpcMicros = data_get($row, 'metrics.averageCpc') ?? data_get($row, 'metrics.average_cpc');
        $cpc = $clicks > 0
            ? round($spend / $clicks, 4)
            : (is_numeric($averageCpcMicros) ? round(((float) $averageCpcMicros) / 1_000_000, 4) : null);

        $status = data_get($row, 'campaign.status');

        return [
            'stat_hour' => $hour->toDateTimeString(),
            'spend' => $spend,
            'impressions' => $impressions,
            'clicks' => $clicks,
            'ctr' => $impressions > 0 ? round($clicks / $impressions, 4) : $this->optionalFloat(data_get($row, 'metrics.ctr')),
            'cpc' => $cpc,
            'platform_status' => is_string($status) && $status !== '' ? $status : null,
            'delivery_status' => null,
            'platform_payload' => $row,
        ];
    }

    private function optionalFloat(mixed $value): ?float
    {
        return is_numeric($value) ? round((float) $value, 4) : null;
    }
}
