<?php

declare(strict_types=1);

namespace PrettyLinks\Pro\Reports;

use PrettyLinks\Repositories\LinkMetas;
use PrettyLinks\Repositories\Links as LinksRepo;
use PrettyLinks\Support\SiteDate;

/**
 * Composes the per-variant split-test payload (metadata + ordered variant
 * rows with clicks / uniques / conversions), independent of the chart
 * time-series.
 *
 * Extracted from `SplitTestController::show()` so both the REST JSON view
 * (which layers the per-variant time-series on top) and the CSV export
 * (`SplitTestCsvExporter`) share one source of truth for variant
 * ordering and the conversion-rate math.
 */
final class SplitTestData
{
    /**
     * Links repository.
     *
     * @var LinksRepo
     */
    private LinksRepo $links;

    /**
     * Link metas repository.
     *
     * @var LinkMetas
     */
    private LinkMetas $linkMetas;

    /**
     * Construct the split-test data composer.
     *
     * @param LinksRepo $links     Links repository.
     * @param LinkMetas $linkMetas Link metas repository.
     */
    public function __construct(LinksRepo $links, LinkMetas $linkMetas)
    {
        $this->links     = $links;
        $this->linkMetas = $linkMetas;
    }

    /**
     * Build the split-test payload for a link, or null when the link does
     * not exist. Variant rows carry no `series` key — the JSON controller
     * adds that separately. When split testing is disabled the payload is
     * returned with an empty `variants` list.
     *
     * @param  integer $linkId  The rotated parent link id.
     * @param  string  $rawFrom Raw start date from the request.
     * @param  string  $rawTo   Raw end date from the request.
     * @return array{
     *     linkId: int,
     *     linkName: string,
     *     linkUrl: string,
     *     linkSlug: string,
     *     goalLinkId: int|null,
     *     goalLinkName: string,
     *     splitTestEnabled: bool,
     *     currentVariantUrls: array<int, string>,
     *     variants: array<int, array<string, mixed>>,
     *     from: string,
     *     to: string
     * }|null
     */
    public function variants(int $linkId, string $rawFrom, string $rawTo): ?array
    {
        $link = $this->links->find($linkId);
        if ($link === null) {
            return null;
        }

        // Resolve as a pair (with ordered ends) so an invalid `from` cannot
        // collapse to "today" while `to` stays in the past and invert the range.
        list($from, $to) = SiteDate::resolvePickerRange(
            $this->leadingYmd($rawFrom),
            $this->leadingYmd($rawTo)
        );

        $report  = new SplitTest($this->links, $this->linkMetas);
        $enabled = $report->isSplitTestEnabled($linkId);

        $payload = [
            'linkId'             => $linkId,
            'linkName'           => (string) ($link['name'] ?? ''),
            'linkUrl'            => (string) ($link['url'] ?? ''),
            'linkSlug'           => (string) ($link['slug'] ?? ''),
            'goalLinkId'         => null,
            'goalLinkName'       => '',
            'splitTestEnabled'   => $enabled,
            'currentVariantUrls' => [],
            'variants'           => [],
            'from'               => $from,
            'to'                 => $to,
        ];

        if (!$enabled) {
            return $payload;
        }

        $goalLinkId = $report->goalLinkId($linkId);
        if ($goalLinkId !== null) {
            $goal = $this->links->find($goalLinkId);
            if ($goal !== null) {
                $payload['goalLinkId']   = $goalLinkId;
                $payload['goalLinkName'] = (string) ($goal['name'] ?? '');
            } else {
                // Configured goal link no longer exists (was hard-deleted).
                // Treat as unconfigured so conversions don't render.
                $goalLinkId = null;
            }
        }

        $current                       = $report->currentVariants($linkId);
        $payload['currentVariantUrls'] = $current;

        $summary = $report->summaryByVariant($linkId, $from, $to);

        $conversions = [];
        if ($goalLinkId !== null) {
            $conversions = (new Conversions())->byVariant($linkId, $goalLinkId, $from, $to);
        }

        // Pivot summary into a URL-keyed map so zero-volume current
        // variants can be filled in below without an extra DB hit.
        $summaryByUrl = [];
        foreach ($summary as $row) {
            $summaryByUrl[$row['url']] = $row;
        }

        // Union of (URLs with clicks) ∪ (current rotation URLs): current
        // variants first (rotation order), then historical-only URLs
        // alphabetically, so the order stays stable across reloads.
        $currentSet = [];
        foreach ($current as $url) {
            $currentSet[$url] = true;
        }
        $orderedUrls = [];
        foreach ($current as $url) {
            $orderedUrls[] = $url;
        }
        $extras = [];
        foreach (array_keys($summaryByUrl) as $url) {
            if (!isset($currentSet[$url])) {
                $extras[] = $url;
            }
        }
        sort($extras);
        foreach ($extras as $url) {
            $orderedUrls[] = $url;
        }

        $variants = [];
        foreach ($orderedUrls as $url) {
            $row     = $summaryByUrl[$url] ?? [
                'url'     => $url,
                'clicks'  => 0,
                'uniques' => 0,
            ];
            $clicks  = (int) $row['clicks'];
            $uniques = (int) $row['uniques'];
            $convs   = isset($conversions[$url]) ? (int) $conversions[$url] : 0;
            // Split-test conv rate uses uniques as denominator (NOT clicks)
            // — distinct from custom-report conv rates. Mirrors v3.
            $convRate = $uniques > 0 ? round(($convs / $uniques) * 100, 2) : 0.0;

            $variants[] = [
                'url'         => (string) $url,
                'clicks'      => $clicks,
                'uniques'     => $uniques,
                'conversions' => $convs,
                'convRate'    => $convRate,
                'isCurrent'   => isset($currentSet[$url]),
            ];
        }

        $payload['variants'] = $variants;
        return $payload;
    }

    /**
     * Leading YYYY-MM-DD from a date or datetime string, or '' when absent.
     * Calendar validation / defaults happen in SiteDate::resolvePickerRange().
     *
     * @param string $value Raw date string from the request.
     */
    private function leadingYmd(string $value): string
    {
        if (preg_match('/^(\d{4}-\d{2}-\d{2})/', trim($value), $m) === 1) {
            return $m[1];
        }

        return '';
    }
}
