<?php

namespace App\Services\Care;

use App\Models\Appointment;
use App\Models\Bill;
use App\Models\Branch;
use App\Models\Drug;
use App\Models\DrugBatch;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\Payment;
use App\Models\Visit;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

/**
 * Facility-admin analytics for Patients / Appointments / Inventory / Billing / Reports hubs.
 * Floor roles keep operational list UIs; admins get data-driven overviews.
 */
class AdminOverviewService
{
    public function __construct(
        protected ReportService $reports,
        protected PharmacyService $pharmacy,
    ) {}

    /**
     * @return array{
     *     period_label: string,
     *     from: string,
     *     to: string,
     *     kpis: list<array{label: string, value: string, hint?: string}>,
     *     breakdowns: list<array{title: string, rows: list<array{label: string, value: string|int, pct?: float|null}>}>,
     *     alerts: list<array{severity: string, message: string}>,
     *     links: list<array{label: string, href: string}>
     * }
     */
    public function patients(Organization $organization, string $ownerRef, ?int $branchId = null): array
    {
        $from = now()->startOfMonth();
        $to = now()->endOfDay();
        $period = $this->reports->patientsReport($ownerRef, $organization->id, $from, $to, $branchId);

        $total = Patient::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->count();

        $today = Patient::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereDate('created_at', today())
            ->count();

        $activeVisits = Visit::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS])
            ->count();

        $byBranch = Patient::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->select('branch_id', DB::raw('count(*) as total'))
            ->groupBy('branch_id')
            ->orderByDesc('total')
            ->limit(8)
            ->get();

        $branchNames = $this->branchNames($organization, $ownerRef, $byBranch->pluck('branch_id'));

        return [
            'period_label' => 'This month',
            'from' => $from->toDateString(),
            'to' => $to->toDateString(),
            'kpis' => [
                ['label' => 'Registered patients', 'value' => number_format($total), 'hint' => 'All time'],
                ['label' => 'New this month', 'value' => number_format($period['new_patients']), 'hint' => 'Registrations'],
                ['label' => 'Visits this month', 'value' => number_format($period['total_visits'])],
                ['label' => 'New today', 'value' => number_format($today)],
                ['label' => 'Open visits', 'value' => number_format($activeVisits), 'hint' => 'In facility now'],
                ['label' => 'Returning this month', 'value' => number_format($period['returning_patients'])],
            ],
            'breakdowns' => [
                [
                    'title' => 'Patients by branch',
                    'rows' => $this->pctRows($byBranch->map(fn ($row) => [
                        'label' => $branchNames[(int) $row->branch_id] ?? 'Unassigned',
                        'value' => (int) $row->total,
                    ])->all(), $total),
                ],
            ],
            'alerts' => $today === 0 && $period['new_patients'] === 0
                ? [['severity' => 'info', 'message' => 'No new patient registrations so far this month.']]
                : [],
            'links' => [
                ['label' => 'Patients report', 'href' => route('care.reports.show', ['type' => 'patients'])],
                ['label' => 'Browse patient records', 'href' => route('care.patients.index', ['view' => 'list'])],
            ],
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function appointments(Organization $organization, string $ownerRef, ?int $branchId = null): array
    {
        $from = now()->startOfMonth();
        $to = now()->endOfDay();
        $period = $this->reports->appointmentsReport($ownerRef, $organization->id, $from, $to, $branchId);

        $baseToday = Appointment::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereDate('scheduled_at', today());

        $waiting = Appointment::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereIn('status', [Appointment::STATUS_CHECKED_IN, Appointment::STATUS_WAITING])
            ->count();

        $inConsult = Appointment::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->where('status', Appointment::STATUS_IN_CONSULTATION)
            ->count();

        $byStatus = Appointment::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereBetween('scheduled_at', [$from, $to])
            ->select('status', DB::raw('count(*) as total'))
            ->groupBy('status')
            ->orderByDesc('total')
            ->get()
            ->keyBy('status');

        $completionRate = $period['total'] > 0
            ? round(($period['completed'] / $period['total']) * 100, 1)
            : 0.0;
        $noShowRate = $period['total'] > 0
            ? round(($period['no_show'] / $period['total']) * 100, 1)
            : 0.0;

        $alerts = [];
        if ($noShowRate >= 15) {
            $alerts[] = [
                'severity' => 'warning',
                'message' => "No-show rate is {$noShowRate}% this month — review booking reminders.",
            ];
        }
        if ($waiting >= 20) {
            $alerts[] = [
                'severity' => 'warning',
                'message' => "{$waiting} patients are waiting across the facility right now.",
            ];
        }

        $appointmentStatusRows = collect(config('care.appointment_statuses', []))
            ->map(fn (string $label, string $status) => [
                'label' => $label,
                'value' => (int) ($byStatus->get($status)?->total ?? 0),
            ])
            ->values()
            ->all();

        return [
            'period_label' => 'This month',
            'from' => $from->toDateString(),
            'to' => $to->toDateString(),
            'kpis' => [
                ['label' => 'Scheduled today', 'value' => number_format((clone $baseToday)->count())],
                ['label' => 'Waiting now', 'value' => number_format($waiting)],
                ['label' => 'In consultation', 'value' => number_format($inConsult)],
                ['label' => 'This month', 'value' => number_format($period['total'])],
                ['label' => 'Completed', 'value' => number_format($period['completed']), 'hint' => $completionRate.'%'],
                ['label' => 'No-shows', 'value' => number_format($period['no_show']), 'hint' => $noShowRate.'%'],
            ],
            'breakdowns' => [
                [
                    'title' => 'Appointments by status (this month)',
                    'rows' => $this->pctRows($appointmentStatusRows, $period['total']),
                ],
            ],
            'alerts' => $alerts,
            'links' => [
                ['label' => 'Appointments report', 'href' => route('care.reports.show', ['type' => 'appointments'])],
                ['label' => 'Browse schedule', 'href' => route('care.appointments.index', ['view' => 'list'])],
            ],
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function inventory(Organization $organization, string $ownerRef, ?int $branchId = null): array
    {
        $drugQuery = Drug::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId));

        $total = (clone $drugQuery)->count();
        $lowStock = $this->pharmacy->lowStock($ownerRef, $organization->id);
        $expired = $this->pharmacy->expiredBatches($ownerRef, $organization->id);

        if ($branchId) {
            $lowStock = $lowStock->where('branch_id', $branchId)->values();
            $expired = $expired->filter(
                fn ($batch) => (int) ($batch->drug?->branch_id ?? 0) === $branchId
            )->values();
        }

        $stockValue = DrugBatch::query()
            ->whereHas('drug', function ($q) use ($ownerRef, $organization, $branchId) {
                $q->owned($ownerRef)
                    ->where('organization_id', $organization->id)
                    ->when($branchId, fn ($dq) => $dq->where('branch_id', $branchId));
            })
            ->where('quantity_on_hand', '>', 0)
            ->where(function ($q) {
                $q->whereNull('expiry_date')->orWhere('expiry_date', '>=', now()->toDateString());
            })
            ->with('drug')
            ->get()
            ->sum(function (DrugBatch $batch) {
                $price = (int) ($batch->drug?->unit_price_minor ?? 0);

                return $price * (int) $batch->quantity_on_hand;
            });

        $expiringSoon = DrugBatch::query()
            ->whereHas('drug', function ($q) use ($ownerRef, $organization, $branchId) {
                $q->owned($ownerRef)
                    ->where('organization_id', $organization->id)
                    ->when($branchId, fn ($dq) => $dq->where('branch_id', $branchId));
            })
            ->where('quantity_on_hand', '>', 0)
            ->whereBetween('expiry_date', [now()->toDateString(), now()->addDays(30)->toDateString()])
            ->count();

        $topLow = $lowStock->take(8)->map(fn (Drug $drug) => [
            'label' => $drug->name,
            'value' => $drug->stockOnHand().' '.$drug->unit,
        ])->all();

        $alerts = [];
        if ($lowStock->isNotEmpty()) {
            $alerts[] = [
                'severity' => 'warning',
                'message' => $lowStock->count().' drug(s) are at or below reorder level.',
            ];
        }
        if ($expired->isNotEmpty()) {
            $alerts[] = [
                'severity' => 'critical',
                'message' => $expired->count().' expired batch(es) still have stock on hand.',
            ];
        }
        if ($expiringSoon > 0) {
            $alerts[] = [
                'severity' => 'info',
                'message' => "{$expiringSoon} batch(es) expire within 30 days.",
            ];
        }

        $currency = strtoupper((string) config('care.billing.currency', config('care.currency', 'GHS')));

        return [
            'period_label' => 'Live stock',
            'from' => now()->toDateString(),
            'to' => now()->toDateString(),
            'kpis' => [
                ['label' => 'Catalog items', 'value' => number_format($total)],
                ['label' => 'Low stock', 'value' => number_format($lowStock->count())],
                ['label' => 'Expired batches', 'value' => number_format($expired->count())],
                ['label' => 'Expiring ≤30 days', 'value' => number_format($expiringSoon)],
                ['label' => 'Est. stock value', 'value' => $currency.' '.number_format($stockValue / 100, 2)],
            ],
            'breakdowns' => [
                [
                    'title' => 'Lowest stock items',
                    'rows' => array_map(fn ($row) => [
                        'label' => $row['label'],
                        'value' => $row['value'],
                        'pct' => null,
                    ], $topLow),
                ],
            ],
            'alerts' => $alerts,
            'links' => [
                ['label' => 'Browse inventory list', 'href' => route('care.pharmacy.drugs.index', ['view' => 'list'])],
            ],
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function billing(Organization $organization, string $ownerRef, ?int $branchId = null): array
    {
        $from = now()->startOfMonth();
        $to = now()->endOfDay();
        $period = $this->reports->financeReport($ownerRef, $organization->id, $from, $to, $branchId);
        $today = $this->reports->dashboardStats($ownerRef, $organization->id, $branchId, true);

        // Current book by status (not limited to invoices created this month —
        // MTD create-date filtering made demos look like "100% Open").
        $byStatus = Bill::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->where('status', '!=', Bill::STATUS_VOID)
            ->select('status', DB::raw('count(*) as total'), DB::raw('sum(balance_minor) as balance'))
            ->groupBy('status')
            ->orderByDesc('total')
            ->get()
            ->keyBy('status');

        $statusLabels = config('care.bill_statuses', []);
        $statusOrder = [
            Bill::STATUS_OPEN,
            Bill::STATUS_PARTIAL,
            Bill::STATUS_PAID,
            Bill::STATUS_DRAFT,
        ];
        $statusTotal = (int) $byStatus->sum(fn ($row) => (int) $row->total);
        $statusRows = [];
        foreach ($statusOrder as $status) {
            if (! array_key_exists($status, $statusLabels) && ! $byStatus->has($status)) {
                continue;
            }
            $count = (int) ($byStatus->get($status)?->total ?? 0);
            $statusRows[] = [
                'label' => $statusLabels[$status] ?? $status,
                'value' => $count,
            ];
        }
        foreach ($byStatus as $status => $row) {
            if (in_array($status, $statusOrder, true)) {
                continue;
            }
            $statusRows[] = [
                'label' => $statusLabels[$status] ?? (string) $status,
                'value' => (int) $row->total,
            ];
        }

        $currency = strtoupper((string) config('care.billing.currency', config('care.currency', 'GHS')));
        $money = fn (int $minor) => $currency.' '.number_format($minor / 100, 2);

        $collectionRate = $period['invoiced_minor'] > 0
            ? round(($period['collected_minor'] / $period['invoiced_minor']) * 100, 1)
            : 0.0;

        $alerts = [];
        if ($period['outstanding_minor'] > 0) {
            $alerts[] = [
                'severity' => 'warning',
                'message' => 'Outstanding balances total '.$money($period['outstanding_minor']).'.',
            ];
        }

        return [
            'period_label' => 'This month',
            'from' => $from->toDateString(),
            'to' => $to->toDateString(),
            'kpis' => [
                ['label' => 'Revenue today', 'value' => $money((int) $today['revenue_today_minor'])],
                ['label' => 'Collected this month', 'value' => $money($period['collected_minor']), 'hint' => $collectionRate.'% of invoiced'],
                ['label' => 'Invoiced this month', 'value' => $money($period['invoiced_minor'])],
                ['label' => 'Outstanding', 'value' => $money($period['outstanding_minor'])],
                ['label' => 'Open / partial bills', 'value' => number_format((int) $today['open_bills'])],
                ['label' => 'Invoices this month', 'value' => number_format($period['invoice_count'])],
            ],
            'breakdowns' => [
                [
                    'title' => 'Invoices by status (current book)',
                    'rows' => $this->pctRows($statusRows, $statusTotal),
                ],
            ],
            'alerts' => $alerts,
            'links' => [
                ['label' => 'Finance report', 'href' => route('care.reports.show', ['type' => 'finance'])],
                ['label' => 'Browse invoices', 'href' => route('care.bills.index', ['view' => 'list'])],
            ],
        ];
    }

    /**
     * Hub snapshot for Reports index.
     *
     * @return array<string, mixed>
     */
    public function reportsHub(Organization $organization, string $ownerRef, ?int $branchId = null): array
    {
        $from = now()->startOfMonth();
        $to = now()->endOfDay();

        $patients = $this->reports->patientsReport($ownerRef, $organization->id, $from, $to, $branchId);
        $appointments = $this->reports->appointmentsReport($ownerRef, $organization->id, $from, $to, $branchId);
        $finance = $this->reports->financeReport($ownerRef, $organization->id, $from, $to, $branchId);
        $lab = $this->reports->laboratoryReport($ownerRef, $organization->id, $from, $to, $branchId);
        $today = $this->reports->dashboardStats($ownerRef, $organization->id, $branchId, true);

        $currency = strtoupper((string) config('care.billing.currency', config('care.currency', 'GHS')));
        $money = fn (int $minor) => $currency.' '.number_format($minor / 100, 2);

        return [
            'period_label' => 'This month',
            'from' => $from->toDateString(),
            'to' => $to->toDateString(),
            // Lead with compact counts for the page hero (first 3); money lives in the grid below.
            'kpis' => [
                ['label' => 'New patients MTD', 'value' => number_format($patients['new_patients'])],
                ['label' => 'Appointments MTD', 'value' => number_format($appointments['total'])],
                ['label' => 'Lab pending', 'value' => number_format($lab['pending'])],
                ['label' => 'Visits MTD', 'value' => number_format($patients['total_visits'])],
                ['label' => 'Revenue today', 'value' => $money((int) $today['revenue_today_minor'])],
                ['label' => 'Collected MTD', 'value' => $money($finance['collected_minor'])],
                ['label' => 'Outstanding', 'value' => $money($finance['outstanding_minor'])],
            ],
            'breakdowns' => [
                [
                    'title' => 'Month-to-date snapshot',
                    'rows' => [
                        ['label' => 'Completed appointments', 'value' => number_format($appointments['completed']), 'pct' => null],
                        ['label' => 'No-shows', 'value' => number_format($appointments['no_show']), 'pct' => null],
                        ['label' => 'Lab completed', 'value' => number_format($lab['completed']), 'pct' => null],
                        ['label' => 'Invoices', 'value' => number_format($finance['invoice_count']), 'pct' => null],
                    ],
                ],
            ],
            'alerts' => [],
            'links' => [],
        ];
    }

    /**
     * @param  list<array{label: string, value: int}>  $rows
     * @return list<array{label: string, value: string|int, pct: float|null}>
     */
    protected function pctRows(array $rows, int $total): array
    {
        return array_map(function (array $row) use ($total) {
            $value = (int) $row['value'];

            return [
                'label' => $row['label'],
                'value' => number_format($value),
                'pct' => $total > 0 ? round(($value / $total) * 100, 1) : null,
            ];
        }, $rows);
    }

    /**
     * @param  Collection<int, mixed>  $ids
     * @return array<int, string>
     */
    protected function branchNames(Organization $organization, string $ownerRef, Collection $ids): array
    {
        return Branch::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->whereIn('id', $ids->filter()->all() ?: [0])
            ->pluck('name', 'id')
            ->mapWithKeys(fn ($name, $id) => [(int) $id => (string) $name])
            ->all();
    }
}
