<?php

namespace App\Services\Care;

use App\Models\Appointment;
use App\Models\Assessment;
use App\Models\AssessmentScore;
use App\Models\Bill;
use App\Models\Diagnosis;
use App\Models\InvestigationRequest;
use App\Models\Patient;
use App\Models\PatientPathway;
use App\Models\Payment;
use App\Models\Visit;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class ReportService
{
    /**
     * @return array<string, mixed>
     */
    public function dashboardStats(
        string $ownerRef,
        int $organizationId,
        ?int $branchId = null,
        bool $includeBilling = true,
    ): array {
        $today = now()->startOfDay();

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

        $appointmentsToday = Appointment::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereDate('scheduled_at', $today)
            ->count();

        $openBills = 0;
        $revenueToday = 0;
        if ($includeBilling) {
            $openBills = Bill::owned($ownerRef)
                ->where('organization_id', $organizationId)
                ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
                ->whereIn('status', [Bill::STATUS_OPEN, Bill::STATUS_PARTIAL])
                ->count();

            $revenueToday = Payment::owned($ownerRef)
                ->where('status', Payment::STATUS_PAID)
                ->whereHas('bill', function (Builder $q) use ($organizationId, $branchId) {
                    $q->where('organization_id', $organizationId);
                    if ($branchId) {
                        $q->where('branch_id', $branchId);
                    }
                })
                ->whereDate('paid_at', $today)
                ->sum('amount_minor');
        }

        $pendingLab = InvestigationRequest::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereIn('status', InvestigationRequest::activeStatuses())
            ->count();

        return [
            'patients_today' => $patientsToday,
            'appointments_today' => $appointmentsToday,
            'open_bills' => $openBills,
            'revenue_today_minor' => (int) $revenueToday,
            'pending_lab' => $pendingLab,
        ];
    }

    /**
     * Today's payment collections for billing dashboards (cashiers / bills.view).
     *
     * @return array{
     *     collected_minor: int,
     *     payment_count: int,
     *     cash_minor: int,
     *     other_minor: int
     * }
     */
    public function todayPaymentStats(
        string $ownerRef,
        int $organizationId,
        ?int $branchId = null,
    ): array {
        $today = now()->startOfDay();

        $base = Payment::owned($ownerRef)
            ->where('status', Payment::STATUS_PAID)
            ->whereHas('bill', function (Builder $q) use ($organizationId, $branchId) {
                $q->where('organization_id', $organizationId);
                if ($branchId) {
                    $q->where('branch_id', $branchId);
                }
            })
            ->whereDate('paid_at', $today);

        $collected = (int) (clone $base)->sum('amount_minor');
        $count = (clone $base)->count();
        $cash = (int) (clone $base)->where('method', Payment::METHOD_CASH)->sum('amount_minor');

        return [
            'collected_minor' => $collected,
            'payment_count' => $count,
            'cash_minor' => $cash,
            'other_minor' => max(0, $collected - $cash),
        ];
    }

    /**
     * Recently paid invoices for billing dashboards.
     *
     * @return Collection<int, Bill>
     */
    public function recentPaidBills(
        string $ownerRef,
        int $organizationId,
        ?int $branchId = null,
        int $limit = 10,
    ): Collection {
        return Bill::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->where('status', Bill::STATUS_PAID)
            ->with('patient')
            ->withMax([
                'payments as last_paid_at' => fn ($q) => $q->where('status', Payment::STATUS_PAID),
            ], 'paid_at')
            ->orderByDesc('last_paid_at')
            ->orderByDesc('id')
            ->limit($limit)
            ->get();
    }

    /**
     * @return array<string, mixed>
     */
    public function patientsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
    {
        $newPatients = Patient::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereBetween('created_at', [$from, $to])
            ->count();

        $returningPatients = Visit::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereBetween('checked_in_at', [$from, $to])
            ->distinct('patient_id')
            ->count('patient_id');

        $visits = Visit::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereBetween('checked_in_at', [$from, $to])
            ->count();

        return [
            'new_patients' => $newPatients,
            'returning_patients' => $returningPatients,
            'total_visits' => $visits,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function appointmentsReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
    {
        $base = Appointment::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereBetween('scheduled_at', [$from, $to]);

        return [
            'total' => (clone $base)->count(),
            'completed' => (clone $base)->where('status', Appointment::STATUS_COMPLETED)->count(),
            'cancelled' => (clone $base)->where('status', Appointment::STATUS_CANCELLED)->count(),
            'no_show' => (clone $base)->where('status', Appointment::STATUS_NO_SHOW)->count(),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function laboratoryReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
    {
        $base = InvestigationRequest::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereBetween('created_at', [$from, $to]);

        $completed = (clone $base)
            ->whereIn('status', [InvestigationRequest::STATUS_COMPLETED, InvestigationRequest::STATUS_DELIVERED])
            ->get();

        $turnaroundHours = $completed
            ->filter(fn ($r) => $r->completed_at && $r->created_at)
            ->map(fn ($r) => $r->created_at->diffInHours($r->completed_at))
            ->avg();

        return [
            'requested' => (clone $base)->count(),
            'completed' => $completed->count(),
            'pending' => (clone $base)->whereIn('status', InvestigationRequest::activeStatuses())->count(),
            'avg_turnaround_hours' => $turnaroundHours ? round($turnaroundHours, 1) : null,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function financeReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to, ?int $branchId = null): array
    {
        $billed = Bill::owned($ownerRef)
            ->where('organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
            ->whereBetween('created_at', [$from, $to])
            ->where('status', '!=', Bill::STATUS_VOID);

        $payments = Payment::owned($ownerRef)
            ->where('status', Payment::STATUS_PAID)
            ->whereHas('bill', function (Builder $q) use ($organizationId, $branchId) {
                $q->where('organization_id', $organizationId);
                if ($branchId) {
                    $q->where('branch_id', $branchId);
                }
            })
            ->whereBetween('paid_at', [$from, $to]);

        return [
            'invoiced_minor' => (int) (clone $billed)->sum('total_minor'),
            'collected_minor' => (int) (clone $payments)->sum('amount_minor'),
            'outstanding_minor' => (int) Bill::owned($ownerRef)
                ->where('organization_id', $organizationId)
                ->when($branchId, fn ($q) => $q->where('branch_id', $branchId))
                ->whereIn('status', [Bill::STATUS_OPEN, Bill::STATUS_PARTIAL])
                ->sum('balance_minor'),
            'invoice_count' => (clone $billed)->count(),
        ];
    }

    /**
     * @return Collection<int, object>
     */
    public function clinicalReport(string $ownerRef, int $organizationId, Carbon $from, Carbon $to): Collection
    {
        return Diagnosis::owned($ownerRef)
            ->whereHas('consultation.visit', function (Builder $q) use ($organizationId, $from, $to) {
                $q->where('organization_id', $organizationId)
                    ->whereBetween('checked_in_at', [$from, $to]);
            })
            ->select('description', DB::raw('count(*) as total'))
            ->groupBy('description')
            ->orderByDesc('total')
            ->limit(20)
            ->get();
    }

    /**
     * Org-level assessment analytics (Enterprise / assessment_analytics).
     *
     * @return array{
     *     summary: array<string, int>,
     *     by_template: Collection<int, object>,
     *     by_pathway: Collection<int, object>,
     *     score_averages: Collection<int, object>
     * }
     */
    public function assessmentsReport(
        string $ownerRef,
        int $organizationId,
        Carbon $from,
        Carbon $to,
        ?int $branchId = null,
    ): array {
        $base = Assessment::query()
            ->from('care_assessments')
            ->where('care_assessments.owner_ref', $ownerRef)
            ->where('care_assessments.organization_id', $organizationId)
            ->when($branchId, fn ($q) => $q->where('care_assessments.branch_id', $branchId))
            ->whereBetween('care_assessments.created_at', [$from, $to])
            ->whereNull('care_assessments.deleted_at');

        $summary = [
            'total_started' => (clone $base)->count(),
            'completed' => (clone $base)->where('care_assessments.status', Assessment::STATUS_COMPLETED)->count(),
            'draft' => (clone $base)->where('care_assessments.status', Assessment::STATUS_DRAFT)->count(),
            'cancelled' => (clone $base)->where('care_assessments.status', Assessment::STATUS_CANCELLED)->count(),
            'patients_with_assessments' => (clone $base)->distinct('care_assessments.patient_id')->count('care_assessments.patient_id'),
            'pathways_activated' => PatientPathway::query()
                ->where('care_patient_pathways.owner_ref', $ownerRef)
                ->where('care_patient_pathways.organization_id', $organizationId)
                ->whereBetween('care_patient_pathways.activated_at', [$from, $to])
                ->whereNull('care_patient_pathways.deleted_at')
                ->count(),
        ];

        $byTemplate = (clone $base)
            ->join('care_assessment_templates', 'care_assessments.template_id', '=', 'care_assessment_templates.id')
            ->select(
                'care_assessment_templates.code as template_code',
                'care_assessment_templates.name as template_name',
                DB::raw('count(*) as total'),
                DB::raw("sum(case when care_assessments.status = 'completed' then 1 else 0 end) as completed"),
            )
            ->groupBy('care_assessment_templates.code', 'care_assessment_templates.name')
            ->orderByDesc('total')
            ->limit(30)
            ->get();

        $byPathway = PatientPathway::query()
            ->from('care_patient_pathways')
            ->where('care_patient_pathways.owner_ref', $ownerRef)
            ->where('care_patient_pathways.organization_id', $organizationId)
            ->whereBetween('care_patient_pathways.activated_at', [$from, $to])
            ->whereNull('care_patient_pathways.deleted_at')
            ->join('care_clinical_pathways', 'care_patient_pathways.pathway_id', '=', 'care_clinical_pathways.id')
            ->select(
                'care_clinical_pathways.code as pathway_code',
                'care_clinical_pathways.name as pathway_name',
                DB::raw('count(*) as total'),
                DB::raw("sum(case when care_patient_pathways.status = 'active' then 1 else 0 end) as still_active"),
            )
            ->groupBy('care_clinical_pathways.code', 'care_clinical_pathways.name')
            ->orderByDesc('total')
            ->limit(20)
            ->get();

        $scoreAverages = AssessmentScore::query()
            ->from('care_assessment_scores')
            ->where('care_assessment_scores.owner_ref', $ownerRef)
            ->whereHas('assessment', function (Builder $q) use ($organizationId, $branchId, $from, $to) {
                $q->where('care_assessments.organization_id', $organizationId)
                    ->when($branchId, fn ($qq) => $qq->where('care_assessments.branch_id', $branchId))
                    ->whereBetween('care_assessments.completed_at', [$from, $to]);
            })
            ->select(
                'care_assessment_scores.template_code',
                DB::raw('count(*) as completions'),
                DB::raw('avg(care_assessment_scores.total_score) as avg_total'),
                DB::raw('avg(care_assessment_scores.max_score) as avg_max'),
            )
            ->groupBy('care_assessment_scores.template_code')
            ->orderByDesc('completions')
            ->limit(20)
            ->get();

        return [
            'summary' => $summary,
            'by_template' => $byTemplate,
            'by_pathway' => $byPathway,
            'score_averages' => $scoreAverages,
        ];
    }
}
