<?php

namespace App\Services;

use App\Models\ReportingUserProfile;
use App\Support\RosterData;
use Carbon\CarbonImmutable;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

class ReportingProfileSyncService
{
    public function sync(string $teamFilter, CarbonImmutable $fromDate, CarbonImmutable $toDate): array
    {
        $teams = $teamFilter === 'all' ? ['qa', 'designers'] : [$teamFilter];
        $metricsByIdentity = [];
        $seenByTeam = [
            'qa' => [],
            'designers' => [],
        ];

        foreach ($teams as $team) {
            $snapshot = $this->fetchSnapshot($team, $fromDate, $toDate);

            if ($snapshot === null) {
                continue;
            }

            $teamMetrics = $team === 'qa'
                ? $this->extractQaMetrics($snapshot)
                : $this->extractDesignMetrics($snapshot);

            foreach ($teamMetrics as $metric) {
                $seenByTeam[$team][] = $metric['name'];
                $metricsByIdentity[$this->identityKey($team, $metric['name'])] = $metric;
            }
        }

        $syncedProfiles = collect();
        foreach ($seenByTeam as $team => $names) {
            $syncedProfiles = $syncedProfiles->concat($this->upsertProfiles($team, $names));
        }

        return [
            'profiles' => $syncedProfiles,
            'metrics' => $metricsByIdentity,
        ];
    }

    /**
     * Cache upstream reporting snapshots briefly so parallel /api/users + /api/users/profiling-metrics
     * (and similar) do not each trigger duplicate HTTP calls to the external reporting API.
     */
    private function fetchSnapshot(string $team, CarbonImmutable $fromDate, CarbonImmutable $toDate): ?array
    {
        $cacheKey = sprintf(
            'reporting-http:%s:%s:%s',
            $team,
            $fromDate->toDateString(),
            $toDate->toDateString()
        );

        $cached = Cache::get($cacheKey);
        if (is_array($cached)) {
            return $cached;
        }

        $result = $this->fetchSnapshotFromRemote($team, $fromDate, $toDate);

        if (is_array($result)) {
            Cache::put($cacheKey, $result, 120);
        }

        return $result;
    }

    private function fetchSnapshotFromRemote(string $team, CarbonImmutable $fromDate, CarbonImmutable $toDate): ?array
    {
        $baseUrl = rtrim((string) config('services.reporting.base_url'), '/');
        $token = (string) config('services.reporting.token');
        $timeout = (int) config('services.reporting.timeout', 30);
        $path = $team === 'qa'
            ? (string) config('services.reporting.qa_path')
            : (string) config('services.reporting.design_path');

        if ($baseUrl === '' || $token === '' || $path === '') {
            return null;
        }

        try {
            $response = Http::baseUrl($baseUrl)
                ->acceptJson()
                ->timeout($timeout)
                ->withToken($token)
                ->get(ltrim($path, '/'), [
                    'from_date' => $fromDate->format('d/m/Y'),
                    'to_date' => $toDate->format('d/m/Y'),
                ]);

            if ($response->failed()) {
                return null;
            }

            return $response->json();
        } catch (ConnectionException) {
            return null;
        }
    }

    private function extractQaMetrics(array $snapshot): array
    {
        $auditors = is_array($snapshot['auditors'] ?? null) ? $snapshot['auditors'] : [];
        $metricsByName = [];

        foreach ($auditors as $auditor) {
            $name = trim((string) ($auditor['auditor_name'] ?? ''));
            if ($name === '') {
                continue;
            }

            $audits = (int) ($auditor['total_audits'] ?? (is_array($auditor['audits'] ?? null) ? count($auditor['audits']) : 0));
            $corrections = (int) ($auditor['total_corrections'] ?? (is_array($auditor['corrections'] ?? null) ? count($auditor['corrections']) : 0));
            $revisions = (int) ($auditor['total_revisions'] ?? (is_array($auditor['revisions'] ?? null) ? count($auditor['revisions']) : 0));
            $matches = (int) ($auditor['date_matched_count'] ?? 0);
            $latest = $this->latestQaActivityDate($auditor);

            if (! isset($metricsByName[$name])) {
                $metricsByName[$name] = [
                    'name' => $name,
                    'audits' => 0,
                    'corrections' => 0,
                    'revisions' => 0,
                    'matches' => 0,
                    'leads' => 0,
                    'images' => 0,
                    'activityCount' => 0,
                    'latestActivityDate' => null,
                ];
            }

            $metricsByName[$name]['audits'] += max($audits, 0);
            $metricsByName[$name]['corrections'] += max($corrections, 0);
            $metricsByName[$name]['revisions'] += max($revisions, 0);
            $metricsByName[$name]['matches'] += max($matches, 0);
            $metricsByName[$name]['activityCount'] += max($audits, 0) + max($corrections, 0) + max($revisions, 0);
            $metricsByName[$name]['latestActivityDate'] = $this->maxDate(
                $metricsByName[$name]['latestActivityDate'],
                $latest
            );
        }

        return array_values($metricsByName);
    }

    private function extractDesignMetrics(array $snapshot): array
    {
        $designers = is_array($snapshot['designers'] ?? null) ? $snapshot['designers'] : [];
        $metricsByName = [];

        foreach ($designers as $designer) {
            $name = trim((string) ($designer['designer_name'] ?? ''));
            if ($name === '') {
                continue;
            }

            $leads = is_array($designer['leads'] ?? null) ? $designer['leads'] : [];
            $revisions = is_array($designer['revisions_list'] ?? null) ? $designer['revisions_list'] : [];
            $images = collect($leads)
                ->sum(fn ($lead) => is_numeric($lead['images'] ?? null) ? (int) $lead['images'] : 0);
            $latest = $this->latestDesignActivityDate($leads, $revisions);

            if (! isset($metricsByName[$name])) {
                $metricsByName[$name] = [
                    'name' => $name,
                    'audits' => 0,
                    'corrections' => 0,
                    'revisions' => 0,
                    'matches' => 0,
                    'leads' => 0,
                    'images' => 0,
                    'activityCount' => 0,
                    'latestActivityDate' => null,
                ];
            }

            $leadCount = count($leads);
            $revisionCount = count($revisions);
            $metricsByName[$name]['leads'] += $leadCount;
            $metricsByName[$name]['revisions'] += $revisionCount;
            $metricsByName[$name]['images'] += $images;
            $metricsByName[$name]['activityCount'] += $leadCount + $revisionCount;
            $metricsByName[$name]['latestActivityDate'] = $this->maxDate(
                $metricsByName[$name]['latestActivityDate'],
                $latest
            );
        }

        return array_values($metricsByName);
    }

    private function upsertProfiles(string $team, array $names): Collection
    {
        $normalizedTeam = RosterData::normalizeTeam($team);
        $uniqueNames = collect($names)
            ->map(fn ($name) => trim((string) $name))
            ->filter()
            ->unique()
            ->values();
        $now = now();
        $profiles = collect();

        foreach ($uniqueNames as $name) {
            $profile = ReportingUserProfile::query()->firstOrNew([
                'name' => $name,
                'team' => $normalizedTeam,
            ]);

            if (! $profile->exists) {
                $profile->first_seen_at = $now;
            }

            $profile->source = $normalizedTeam === 'qa' ? 'qa_reporting' : 'design_reporting';
            $profile->last_seen_at = $now;
            $profile->save();

            $profiles->push($profile);
        }

        return $profiles;
    }

    public function identityKey(string $team, string $name): string
    {
        return RosterData::normalizeTeam($team).'|'.RosterData::normalizeName($name);
    }

    private function latestQaActivityDate(array $auditor): ?string
    {
        $candidates = [];

        foreach ((array) ($auditor['audits'] ?? []) as $row) {
            $candidates[] = $row['audit_timestamp'] ?? null;
            $candidates[] = $row['time_for_filter'] ?? null;
        }

        foreach ((array) ($auditor['corrections'] ?? []) as $row) {
            $candidates[] = $row['correction_audit_last_modified'] ?? null;
            $candidates[] = $row['time_for_filter'] ?? null;
        }

        foreach ((array) ($auditor['revisions'] ?? []) as $row) {
            $candidates[] = $row['revision_audit_last_modified'] ?? null;
            $candidates[] = $row['time_for_filter'] ?? null;
        }

        return $this->maxDate(...$candidates);
    }

    private function latestDesignActivityDate(array $leads, array $revisions): ?string
    {
        $candidates = collect($leads)
            ->map(fn ($lead) => $lead['lead_date'] ?? null)
            ->concat(collect($revisions)->map(fn ($revision) => $revision['revision_date'] ?? null))
            ->all();

        return $this->maxDate(...$candidates);
    }

    private function maxDate(?string ...$dates): ?string
    {
        $maxDate = null;

        foreach ($dates as $value) {
            if (blank($value)) {
                continue;
            }

            try {
                $normalized = CarbonImmutable::parse($value)->toDateString();
            } catch (\Throwable) {
                continue;
            }

            if ($maxDate === null || $normalized > $maxDate) {
                $maxDate = $normalized;
            }
        }

        return $maxDate;
    }
}

