<?php

namespace Ignite\Core\Services\Google\Analytics\Adapter;

use Carbon\CarbonPeriod;
use Google\Client as GoogleClient;
use Google\Collection as GoogleCollection;
use Google\Service\AnalyticsData;
use Google\Service\AnalyticsData\DateRange;
use Google\Service\AnalyticsData\Dimension;
use Google\Service\AnalyticsData\Metric;
use Google\Service\AnalyticsData\RunReportRequest;
use Google\Service\AnalyticsData\RunReportResponse;
use GuzzleHttp\Exception\ConnectException;
use Ignite\Core\Services\Google\Analytics\Adapter\AdapterInterface;
use Illuminate\Support\Carbon;

class AdapterGA4 implements AdapterInterface
{
    protected const DIMENSION_PREVIOUS_DATERANGE = 'date_range_0';
    protected const DIMENSION_CURRENT_DATERANGE = 'date_range_1';

    /**
     * @var GoogleClient
     */
    protected $client;

    /**
     * @param GoogleClient $client
     */
    public function __construct(GoogleClient $client)
    {
        $this->client = $client;
    }

    /**
     * {@inheritdoc}
     */
    public function fetchChangingTotals(GoogleCollection $response): array
    {
        // $response->getTotals() is empty in GA4, so we manually get total
        $previous = $this->getDimensionMetric($response, self::DIMENSION_PREVIOUS_DATERANGE);
        $current = $this->getDimensionMetric($response, self::DIMENSION_CURRENT_DATERANGE);

        return [$current, $previous];
    }

    /**
     * {@inheritdoc}
     */
    public function fetchResponse(
        string $metricName,
        Carbon $start,
        Carbon $end,
        Carbon $previousStart = null,
        Carbon $previousEnd = null
    ): RunReportResponse {
        $propertyId = config('core.google.analytics.property');
        if (!$propertyId) {
            throw new \Google\Exception('Google Analytics Property ID is not set.');
        }

        $currentPeriod = new DateRange();
        $currentPeriod->setStartDate($start->format('Y-m-d'));
        $currentPeriod->setEndDate($end->format('Y-m-d'));

        $previousPeriod = new DateRange();
        $previousPeriod->setStartDate($previousStart->format('Y-m-d'));
        $previousPeriod->setEndDate($previousEnd->format('Y-m-d'));

        $metric = new Metric();
        // expression can be a math formula of multiple metrics. if name == metricName, then do not send expression
        // $metric->setExpression($metricName);
        // name can be an alias if we are using an 'expression'
        $metric->setName($metricName);
        $metric->setInvisible(false);

        $dimension = new Dimension();
        $dimension->setName('day');

        $request = new RunReportRequest();
        $request->setDateRanges([
            $previousPeriod,  // date_range_0 => DIMENSION_PREVIOUS_DATERANGE
            $currentPeriod,   // date_range_1 => DIMENSION_CURRENT_DATERANGE
        ]);
        $request->setMetrics([$metric]);
        $request->setDimensions([$dimension]);

        // setProperty() may be optional but good to have as well
        $request->setProperty($propertyId);

        try {
            // notice the 'property' parameter should be "properties/{$propertyId}" and not just $propertyId
            $response = $this->service()->properties->runReport('properties/' . $propertyId, $request);
        } catch (ConnectException $connectException) {
            logger()->error($connectException->getMessage(), $connectException->getHandlerContext());
            return null;
        }

        return $response;
    }

    /**
     * {@inheritdoc}
     */
    public function fetchSeries(GoogleCollection $response, Carbon $start, Carbon $end): array
    {
        $series = [];
        // $dimensions = [];

        $intervals = CarbonPeriod::create($start, $end);
        foreach ($intervals as $interval) {
            $key = $interval->format('d');
            $series[$key] = 0;
        }

        if (! empty($response) && ! empty($response->getRows())) {
            /** @var \Google\Service\AnalyticsData\Row $row */
            foreach ($response->getRows() as $row) {
                $dimensionValues = $row->getDimensionValues();
                $dimensionKey = $dimensionValues[0]->getvalue();
                $dimensionName = $dimensionValues[1]->getvalue();

                // $dimensions[$dimensionName][$dimensionKey] = $row->getMetricValues()[0]->getValue();
                if (self::DIMENSION_CURRENT_DATERANGE === $dimensionName
                    && isset($series[$dimensionKey])
                ) {
                    // Note: sometimes there is no data for a certain dates, especially the end date
                    $series[$dimensionKey] = $row->getMetricValues()[0]->getValue();
                }
            }
        }

        return $series;
    }

    /**
     * {@inheritdoc}
     */
    public function metricValue(GoogleCollection $response): float
    {
        // $response->getTotals() is empty in GA4, so we manually get total
        return empty($response)
            ? 0
            : $this->getDimensionMetric($response, self::DIMENSION_CURRENT_DATERANGE);
    }

    /**
     * Get the single value of one dimension from the response.
     *
     * @param  GoogleCollection $response
     * @param  string           $dimension
     * @return float
     */
    protected function getDimensionMetric(GoogleCollection $response, string $dimension): float
    {
        $total = 0;
        $count = 0;
        $metricName = $response->getMetricHeaders()[0]->getName();

        if (empty($response) || empty($response->getRows())) {
            return $total;
        }

        /** @var \Google\Service\AnalyticsData\Row $row */
        foreach ($response->getRows() as $row) {
            $dimensionValues = $row->getDimensionValues();
            // $dimensionValues[0] contains the key (i.e. date)
            // whereas $dimensionValues[1] contains the dimension name.
            if ($dimension === $dimensionValues[1]->getvalue()) {
                $total += $row->getMetricValues()[0]->getValue();
                $count++;
            }
        }

        /* Note: Sometimes there is no data for a certain dates,
         * especially the end date. So the average may only
         * count for the dates we do have instead of 0 for the
         * missing dates, which is close to how UA did it.
         * -------------------------------------------------- */
        if ('bounceRate' === $metricName) {
            return $this->getMetricBounceRate($total, $count);
        }
        if ('averageSessionDuration' === $metricName) {
            return $this->getMetricSessionDuration($total, $count);
        }

        return $total;
    }

    /**
     * Get the bounce rate average (x 100)
     *
     * @param  float  $total
     * @param  float  $count
     * @return float
     */
    protected function getMetricBounceRate(float $total, float $count): float
    {
        return ($count != 0) ? $total / $count * 100 : 0;
    }

    /**
     * Get the average session duration in seconds.
     *
     * @param  float  $total
     * @param  float  $count
     * @return int
     */
    protected function getMetricSessionDuration(float $total, float $count): int
    {
        return ($count != 0) ? round($total / $count, 0) : 0;
    }

    /**
     * Instantiate the Google Service for the API.
     *
     * @param  Google_Client|null $client
     * @return AnalyticsData
     */
    protected function service(?Google_Client $client = null): AnalyticsData
    {
        $scopes = [
            'https://www.googleapis.com/auth/analytics.readonly'
        ];

        $client = $client ?? $this->client;
        $client->setApplicationName("Ignite 3");
        $client->setAuthConfig(config('core.google.analytics.key_file'));
        $client->setScopes($scopes);

        return new AnalyticsData($client);
    }
}
