<?php

namespace App\Components\Report;

use App\Models\KpiTarget;
use App\Models\LeadSource\LeadSource;
use App\Models\Solicitor\Solicitor;
use App\Services\Dashboard\DashboardMetrics as M;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Carbon;
use Livewire\Attributes\Url;
use Livewire\Component;

/**
 * KPIs report: every dashboard metric shown as a time series across three tabs —
 * Day (last 12 days), Week (last 12 weeks) and Month (last 12 months). Metrics
 * are rows, periods are columns (oldest → newest), matching the KPI spreadsheet
 * tab. Filterable by lead source.
 *
 * All calculations come from App\Services\Dashboard\DashboardMetrics so the
 * figures reconcile with the live dashboard. Throughput/derived metrics are
 * computed from history and cover the full range; "currently in status" (stock)
 * metrics are read from the daily 5pm snapshots, so they populate only as far
 * back as snapshots exist (fully on the Day tab, and forward from there).
 */
class KpiReport extends Component
{
    use AuthorizesRequests;

    #[Url]
    public ?int $leadSourceId = null;

    public string $activeTab = 'day';

    /**
     * Editable targets, keyed by granularity then metric key:
     * $targets['day']['leads_in'] = 10. Bound to the inline Target inputs.
     */
    public array $targets = [];

    public function mount(): void
    {
        $this->authorize('reports.trends');
        $this->loadTargets();
    }

    public function setActiveTab(string $tab): void
    {
        if (in_array($tab, ['day', 'week', 'month'], true)) {
            $this->activeTab = $tab;
        }
    }

    /**
     * Hydrate $targets from the kpi_targets table.
     */
    private function loadTargets(): void
    {
        $this->targets = ['day' => [], 'week' => [], 'month' => []];

        foreach (KpiTarget::all() as $target) {
            $this->targets[$target->granularity][$target->metric_key] = $target->value;
        }
    }

    /**
     * Persist an inline target edit. Livewire passes the dotted path, e.g.
     * "day.leads_in". A blank/non-numeric value clears the target.
     */
    public function updatedTargets($value, string $key): void
    {
        $this->authorize('reports.trends');

        [$granularity, $metricKey] = array_pad(explode('.', $key, 2), 2, null);

        if (! in_array($granularity, ['day', 'week', 'month'], true) || $metricKey === null) {
            return;
        }

        $value = trim((string) $value);

        if ($value === '' || ! is_numeric($value)) {
            KpiTarget::where('granularity', $granularity)->where('metric_key', $metricKey)->delete();
            $this->targets[$granularity][$metricKey] = null;

            return;
        }

        KpiTarget::updateOrCreate(
            ['granularity' => $granularity, 'metric_key' => $metricKey],
            ['value' => (float) $value],
        );

        $this->targets[$granularity][$metricKey] = (float) $value;
    }

    public function render()
    {
        $metrics = new M(app(Solicitor::class)?->id, $this->leadSourceId ?: null);

        return view('components.report.kpi-report', [
            'day'         => $this->build($metrics, 'day'),
            'week'        => $this->build($metrics, 'week'),
            'month'       => $this->build($metrics, 'month'),
            'leadSources' => LeadSource::where('status', 'active')->orderBy('display_name')->get(['id', 'display_name']),
        ]);
    }

    /**
     * Build one granularity: ordered period labels + a row per metric.
     */
    private function build(M $m, string $granularity): array
    {
        [$ranges, $sqlFmt, $phpFmt] = $this->ranges($granularity);

        $keys     = array_map(fn ($r) => $r['key'], $ranges);
        $labels   = array_map(fn ($r) => $r['label'], $ranges);
        $fullFrom = $ranges[0]['from'];
        $fullTo   = Carbon::now();

        // Base throughput maps (one grouped query each over the whole range).
        $leads       = $m->createdByPeriod($sqlFmt, $fullFrom, $fullTo);
        $contacted   = $m->contactMadeByPeriod($sqlFmt, $fullFrom, $fullTo);
        $cSent       = $m->contractsSentByPeriod($sqlFmt, $fullFrom, $fullTo);
        $cSigned     = $m->contractsSignedByPeriod($sqlFmt, $fullFrom, $fullTo);
        $redress     = $m->changedToStatusByPeriod(M::STATUS_SENT_TO_REDRESS, $sqlFmt, $fullFrom, $fullTo);
        $appRecv     = $m->changedToStatusByPeriod(M::STATUS_APP_NUMBER, $sqlFmt, $fullFrom, $fullTo);
        $readyDsar   = $m->changedToStatusByPeriod(M::STATUS_INVESTIGATION, $sqlFmt, $fullFrom, $fullTo);
        $dsarClients = $m->clientsWithDsarSentByPeriod($sqlFmt, $fullFrom, $fullTo);
        $dsarSent    = $m->dsarsSentByPeriod($sqlFmt, $fullFrom, $fullTo);
        $partDsar    = $m->changedToStatusByPeriod(M::STATUS_PART_DSAR, $sqlFmt, $fullFrom, $fullTo);
        $closed      = $m->changedToStatusByPeriod(M::STATUS_CLOSED, $sqlFmt, $fullFrom, $fullTo);
        $tAnother    = $m->closedWithTagByPeriod(M::TAG_CLOSED_ANOTHER_FIRM, $sqlFmt, $fullFrom, $fullTo);
        $tNoContact  = $m->closedWithTagByPeriod(M::TAG_CLOSED_NO_CONTACT, $sqlFmt, $fullFrom, $fullTo);
        $tNoCriteria = $m->closedWithTagByPeriod(M::TAG_CLOSED_NO_CRITERIA, $sqlFmt, $fullFrom, $fullTo);
        $tDuplicate  = $m->closedWithTagByPeriod(M::TAG_CLOSED_DUPLICATE, $sqlFmt, $fullFrom, $fullTo);

        // Stock maps from the daily snapshots.
        $sWelcome  = $m->snapshotByPeriod('at_welcome_call', $phpFmt, $fullFrom, $fullTo);
        $sWaiting  = $m->snapshotByPeriod('waiting_on_app_number', $phpFmt, $fullFrom, $fullTo);
        $sMissing  = $m->snapshotByPeriod('missing_id_poa', $phpFmt, $fullFrom, $fullTo);
        $sInvest   = $m->snapshotByPeriod('investigation_dsar_activity', $phpFmt, $fullFrom, $fullTo);
        $sActively = $m->snapshotByPeriod('actively_claiming', $phpFmt, $fullFrom, $fullTo);

        // Projection helpers onto the ordered period keys.
        $countRow = fn (array $map) => array_map(fn ($k) => (int) ($map[$k] ?? 0), $keys);
        $stockRow = fn (array $map) => array_map(fn ($k) => $map[$k] ?? null, $keys);
        $rateRow  = fn (array $num, array $den) => array_map(function ($k) use ($num, $den) {
            $d = (int) ($den[$k] ?? 0);
            return $d === 0 ? null : round((int) ($num[$k] ?? 0) / $d * 100, 1);
        }, $keys);
        // Plain average (num ÷ den, not a percentage) — e.g. DSARs per client.
        $avgPerRow = fn (array $num, array $den) => array_map(function ($k) use ($num, $den) {
            $d = (int) ($den[$k] ?? 0);
            return $d === 0 ? null : round((int) ($num[$k] ?? 0) / $d, 1);
        }, $keys);

        // Average APP lead time is per-claim paired, so computed per period.
        $avgRow = array_map(fn ($r) => $m->avgAppLeadTime($r['from'], $r['to']), $ranges);

        $rows = [
            ['key' => 'leads_in', 'label' => 'Leads In', 'sub' => '(created)', 'type' => 'int', 'note' => 'Number of new clients added to the system', 'values' => $countRow($leads)],
            ['key' => 'client_contacted', 'label' => 'Client Contacted', 'sub' => '', 'type' => 'int', 'note' => 'Clients marked Contact Made via the toggle', 'values' => $countRow($contacted)],
            ['key' => 'lead_to_contacted_rate', 'label' => 'Lead In to Contacted rate', 'sub' => '', 'type' => 'pct', 'note' => 'Client Contacted ÷ Leads In', 'values' => $rateRow($contacted, $leads)],
            ['key' => 'at_welcome_call', 'label' => 'At Welcome Call', 'sub' => '', 'type' => 'stock', 'note' => 'Clients in status Welcome Call (5pm snapshot)', 'values' => $stockRow($sWelcome)],
            ['key' => 'contracts_sent', 'label' => 'Contracts Sent', 'sub' => '', 'type' => 'int', 'note' => 'Clients with contracts sent via eSign', 'values' => $countRow($cSent)],
            ['key' => 'contacted_to_contracts_sent_rate', 'label' => 'Client Contacted to Contracts Sent rate', 'sub' => '', 'type' => 'pct', 'note' => 'Contracts Sent ÷ Client Contacted', 'values' => $rateRow($cSent, $contacted)],
            ['key' => 'contracts_signed', 'label' => 'Contracts Signed', 'sub' => '', 'type' => 'int', 'note' => 'Clients signing contracts', 'values' => $countRow($cSigned)],
            ['key' => 'signup_rate', 'label' => 'Sign up rate', 'sub' => '', 'type' => 'pct', 'note' => 'Contracts Signed ÷ Contracts Sent', 'values' => $rateRow($cSigned, $cSent)],
            ['key' => 'sent_to_redress', 'label' => 'Sent To Redress', 'sub' => '(Parts 1/3)', 'type' => 'int', 'note' => 'Status changed to Part 1/3 Sent To Redress', 'values' => $countRow($redress)],
            ['key' => 'lead_to_redress_rate', 'label' => 'Lead In to Sent To Redress rate', 'sub' => '', 'type' => 'pct', 'note' => 'Sent To Redress ÷ Leads In', 'values' => $rateRow($redress, $leads)],
            ['key' => 'waiting_on_app_number', 'label' => 'Waiting on APP Number', 'sub' => '', 'type' => 'stock', 'note' => 'Clients in status Parts 1/3 Sent To Redress (5pm snapshot)', 'values' => $stockRow($sWaiting)],
            ['key' => 'app_number_received', 'label' => 'APP Number Received', 'sub' => '', 'type' => 'int', 'note' => 'Status changed to APP Number Received', 'values' => $countRow($appRecv)],
            ['key' => 'avg_app_lead_time', 'label' => 'Average APP Number Lead Time', 'sub' => '', 'type' => 'days', 'note' => 'Avg days between Sent To Redress and APP Number Received', 'values' => $avgRow],
            ['key' => 'missing_id_poa', 'label' => 'Missing ID/POA', 'sub' => '', 'type' => 'stock', 'note' => 'Clients in status Missing ID/POA (5pm snapshot)', 'values' => $stockRow($sMissing)],
            ['key' => 'ready_for_dsar', 'label' => 'Ready For DSAR', 'sub' => '', 'type' => 'int', 'note' => 'Status changed to Investigation - DSAR Activity', 'values' => $countRow($readyDsar)],
            ['key' => 'clients_dsar_sent', 'label' => 'Clients DSAR Sent', 'sub' => '', 'type' => 'int', 'note' => 'Distinct clients with at least one DSAR sent (created, past draft/approved)', 'values' => $countRow($dsarClients)],
            ['key' => 'investigation_dsar_activity', 'label' => 'Investigation - DSAR Activity', 'sub' => '', 'type' => 'stock', 'note' => 'Clients in status Investigation - DSAR Activity (5pm snapshot)', 'values' => $stockRow($sInvest)],
            ['key' => 'individual_dsars_sent', 'label' => 'Individual DSARs sent', 'sub' => '', 'type' => 'int', 'note' => 'DSARs sent (created, past draft/approved)', 'values' => $countRow($dsarSent)],
            ['key' => 'avg_dsars_per_client', 'label' => 'Average DSARs per client', 'sub' => '', 'type' => 'ratio', 'note' => 'Individual DSARs sent ÷ Clients DSAR Sent', 'values' => $avgPerRow($dsarSent, $dsarClients)],
            ['key' => 'part_dsar_received', 'label' => 'Part Dsar Received', 'sub' => '', 'type' => 'int', 'note' => 'Status changed to Part Dsar Received', 'values' => $countRow($partDsar)],
            ['key' => 'actively_claiming', 'label' => 'Actively Claiming', 'sub' => '', 'type' => 'stock', 'note' => 'Investigation - DSAR Activity or greater and not closed (5pm snapshot)', 'values' => $stockRow($sActively)],
            ['key' => 'closed', 'label' => 'Closed', 'sub' => '', 'type' => 'int', 'note' => 'Status changed to Closed', 'values' => $countRow($closed)],
            ['key' => 'closed_another_firm', 'label' => 'Closed', 'sub' => '— with another firm', 'type' => 'int', 'note' => 'Closed with applicable tag', 'values' => $countRow($tAnother)],
            ['key' => 'closed_no_contact', 'label' => 'Closed', 'sub' => '— no contact', 'type' => 'int', 'note' => 'Closed with applicable tag', 'values' => $countRow($tNoContact)],
            ['key' => 'closed_no_criteria', 'label' => 'Closed', 'sub' => '— does not meet criteria', 'type' => 'int', 'note' => 'Closed with applicable tag', 'values' => $countRow($tNoCriteria)],
            ['key' => 'closed_duplicate', 'label' => 'Closed', 'sub' => '— duplicate', 'type' => 'int', 'note' => 'Closed with applicable tag', 'values' => $countRow($tDuplicate)],
        ];

        return ['labels' => $labels, 'rows' => $rows];
    }

    /**
     * Ordered period ranges for a granularity, with the SQL/Carbon format tokens
     * used to key the buckets. Returns [$ranges, $sqlFmt, $phpFmt].
     */
    private function ranges(string $granularity): array
    {
        $now    = Carbon::now();
        $ranges = [];

        if ($granularity === 'day') {
            for ($i = 11; $i >= 0; $i--) {
                $d = $now->copy()->subDays($i);
                $ranges[] = ['key' => $d->format('Y-m-d'), 'label' => $d->format('j M'), 'from' => $d->copy()->startOfDay(), 'to' => $d->copy()->endOfDay()];
            }

            return [$ranges, '%Y-%m-%d', 'Y-m-d'];
        }

        if ($granularity === 'week') {
            for ($i = 11; $i >= 0; $i--) {
                $s = $now->copy()->startOfWeek()->subWeeks($i);
                $ranges[] = ['key' => $s->format('o-W'), 'label' => $s->format('j M'), 'from' => $s->copy(), 'to' => $s->copy()->endOfWeek()];
            }

            return [$ranges, '%x-%v', 'o-W'];
        }

        for ($i = 11; $i >= 0; $i--) {
            $s = $now->copy()->startOfMonth()->subMonths($i);
            $ranges[] = ['key' => $s->format('Y-m'), 'label' => $s->format('M y'), 'from' => $s->copy(), 'to' => $s->copy()->endOfMonth()];
        }

        return [$ranges, '%Y-%m', 'Y-m'];
    }
}
