<?php

namespace App\Http\Controllers;

use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Models\Payment;
use App\Domain\Identity\Services\PermissionService;
use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Context\BranchContext;
use App\Domain\Shared\Services\ReportService;
use App\Domain\Training\Models\Enrollment;
use App\Livewire\Reports\ReportViewer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Symfony\Component\HttpFoundation\StreamedResponse;

/**
 * CSV exports of the operational lists.
 *
 * None of these methods filters on branch by hand any more. Every model they
 * read — Participant, Payment, Invoice, Enrollment — carries BranchScope, so
 * the rows are narrowed to the active branch before the query is built, and a
 * second filter here could only ever disagree with it. The one that used to be
 * here did: invoices were filtered by the billable participant's *current*
 * branch, so a transferred player dragged their old branch's invoices into the
 * new branch's export and every walk-in invoice (no participant at all) was
 * dropped from both.
 */
class ExportController extends Controller
{
    public function participants(Request $request): StreamedResponse
    {
        Gate::authorize('participants.list');

        $user = auth()->user();

        $query = Participant::with(['person', 'branch', 'primaryActivity'])
            ->when($request->search, function ($q) use ($request) {
                $search = $request->search;
                $q->where(function ($q2) use ($search) {
                    $q2->where('participant_number', 'ilike', "%{$search}%")
                       ->orWhere('membership_id', 'ilike', "%{$search}%")
                       ->orWhereHas('person', function ($pq) use ($search) {
                           $pq->where('name_ar', 'ilike', "%{$search}%")
                              ->orWhere('name', 'ilike', "%{$search}%")
                              ->orWhere('phone', 'like', "%{$search}%")
                              ->orWhere('national_id', 'like', "%{$search}%");
                       });
                });
            })
            ->when($request->status, fn ($q, $s) => $q->where('status', $s))
            ->when($request->activity, fn ($q, $a) => $q->where('primary_activity_id', $a))
            ->when($request->skill_level, fn ($q, $s) => $q->where('skill_level', $s))
            ->when($request->membership_type, fn ($q, $m) => $q->where('membership_type', $m))
            ->when($request->is_free !== null && $request->is_free !== '', fn ($q) => $q->where('is_free', $request->is_free === '1'));

        app(PermissionService::class)->applyScope($query, $user, 'participants.list');

        $allowedSorts = ['name_ar', 'participant_number', 'primary_activity_id', 'membership_type', 'status', 'created_at'];
        $sortBy = in_array($request->get('sort_by'), $allowedSorts) ? $request->get('sort_by') : 'name_ar';
        $sortDir = in_array($request->get('sort_dir'), ['asc', 'desc']) ? $request->get('sort_dir') : 'asc';

        if ($sortBy === 'name_ar') {
            $query->join('people', 'participants.person_id', '=', 'people.id')
                  ->orderBy('people.name_ar', $sortDir)
                  ->select('participants.*');
        } else {
            $query->orderBy($sortBy, $sortDir);
        }

        return $this->streamCsv('participants', [
            'الاسم', 'الهاتف', 'البريد', 'الحالة', term('membership_type'), term('membership_id'), 'مجاني', 'الفرع', 'النشاط', 'تاريخ التسجيل',
        ], $query, function ($p) {
            return [
                $p->person?->name_ar ?? $p->person?->name ?? '',
                $p->person?->phone ?? '',
                $p->person?->email ?? '',
                $p->status?->value ?? $p->status ?? '',
                // The header is Arabic prose, so this file is read by a human,
                // not re-imported — it gets the branch's word rather than the
                // stored 'member' / 'non_member'.
                membership_label($p->membership_type?->value ?? $p->membership_type, $p->branch_id),
                $p->membership_id ?? '',
                $p->is_free ? 'نعم' : 'لا',
                $p->branch?->name_ar ?? '',
                $p->primaryActivity?->name_ar ?? '',
                $p->created_at?->format('Y-m-d'),
            ];
        });
    }

    public function payments(Request $request): StreamedResponse
    {
        Gate::authorize('invoices.list');

        $from = $request->get('from', now()->startOfMonth()->toDateString());
        $to = $request->get('to', now()->toDateString());

        $query = Payment::with(['invoice', 'creator'])
            ->where('status', 'confirmed')
            ->whereBetween('payment_date', [$from, $to])
            ->orderByDesc('payment_date');

        return $this->streamCsv('payments', [
            'التاريخ', 'المرجع', 'المبلغ', 'الطريقة', 'رقم الفاتورة', 'بواسطة',
        ], $query, function ($p) {
            return [
                $p->payment_date?->format('Y-m-d'),
                $p->reference ?? '',
                number_format($p->amount / 100, 2),
                $p->method?->value ?? '',
                $p->invoice?->number ?? '',
                $p->creator?->name ?? '',
            ];
        });
    }

    public function invoices(Request $request): StreamedResponse
    {
        Gate::authorize('invoices.list');

        $query = Invoice::with(['billable.person'])
            ->when($request->status, fn ($q, $s) => $q->where('status', $s))
            ->when($request->search, function ($q) use ($request) {
                $search = $request->search;
                $q->where(function ($q2) use ($search) {
                    $q2->where('number', 'ilike', "%{$search}%")
                       ->orWhere('contact_name', 'ilike', "%{$search}%");
                });
            })
            ->when($request->from, fn ($q, $from) => $q->where('created_at', '>=', $from))
            ->when($request->to, fn ($q, $to) => $q->where('created_at', '<=', $to . ' 23:59:59'))
            ->orderByDesc('created_at');

        return $this->streamCsv('invoices', [
            'رقم الفاتورة', 'المشترك', 'الإجمالي', 'المدفوع', 'المستحق', 'الحالة', 'التاريخ', 'تاريخ الاستحقاق',
        ], $query, function ($inv) {
            $name = $inv->contact_name
                ?? $inv->billable?->person?->name_ar
                ?? $inv->billable?->name_ar
                ?? '';
            return [
                $inv->number ?? '',
                $name,
                number_format($inv->total_amount / 100, 2),
                number_format($inv->paid_amount / 100, 2),
                number_format(($inv->total_amount - $inv->paid_amount) / 100, 2),
                $inv->status?->value ?? $inv->status ?? '',
                $inv->created_at?->format('Y-m-d'),
                $inv->due_date?->format('Y-m-d') ?? '',
            ];
        });
    }

    public function enrollments(Request $request): StreamedResponse
    {
        Gate::authorize('enrollments.list');

        $query = Enrollment::with(['participant.person', 'group.program', 'group.branch'])
            ->when($request->status, fn ($q, $s) => $q->where('status', $s))
            ->orderByDesc('enrollment_date');

        return $this->streamCsv('enrollments', [
            'المشترك', 'البرنامج', 'المجموعة', 'الحالة', 'تاريخ التسجيل', 'الفرع',
        ], $query, function ($e) {
            return [
                $e->participant?->person?->name_ar ?? '',
                $e->group?->program?->name_ar ?? '',
                $e->group?->name_ar ?? '',
                $e->status?->value ?? $e->status ?? '',
                $e->enrollment_date?->format('Y-m-d') ?? '',
                $e->group?->branch?->name_ar ?? '',
            ];
        });
    }

    public function report(Request $request, ReportService $reportService): StreamedResponse
    {
        Gate::authorize('reports.view');

        $reportKey = $request->get('report');
        $viewer = new ReportViewer();
        $configs = $viewer->getReportConfig();

        if (!$reportKey || !isset($configs[$reportKey])) {
            abort(404);
        }

        $config = $configs[$reportKey];
        $method = $config['method'];
        $usesDates = $config['uses_dates'] ?? true;
        // ReportService is a domain service and may not read the session, so
        // the branch is resolved here and passed in. It must come from
        // BranchContext: session()->has() is `! is_null(get($key))`, which
        // reports false for the null that means "all branches", so reading the
        // key raw silently narrowed a cross-branch user's report to their own
        // branch and presented it as the whole picture.
        $branchId = app(BranchContext::class)->branchId();

        $from = $request->get('from', now()->startOfMonth()->toDateString());
        $to = $request->get('to', now()->toDateString());

        // The report's own filters, read from the query string the viewer put
        // them in and passed positionally in the order the report declares
        // them. Built from the same list the page uses, so a CSV cannot quietly
        // be of something other than what is on screen — which is exactly what
        // happened while the minimum-absences filter was a special case here
        // and not there.
        $extra = [];

        foreach ($config['extra_filters'] ?? [] as $filter) {
            $extra[] = $request->get($filter);
        }

        // A filter the request never carried is dropped rather than passed as
        // null, so the service method's own default applies. Only from the tail
        // — the arguments are positional, so an interior gap would shift every
        // filter after it onto the wrong parameter.
        while ($extra !== [] && end($extra) === null) {
            array_pop($extra);
        }

        if ($usesDates) {
            $data = $reportService->$method($from, $to, $branchId, ...$extra);
        } else {
            $data = $reportService->$method($branchId, ...$extra);
        }

        $moneyCols = $config['money_cols'] ?? [];
        $columns = $config['columns'];

        $date = now()->format('Y-m-d');

        return response()->streamDownload(function () use ($config, $data, $columns, $moneyCols) {
            $handle = fopen('php://output', 'w');
            fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));
            fputcsv($handle, $config['headers']);

            foreach ($data as $row) {
                $csvRow = [];
                foreach ($columns as $col) {
                    $value = is_array($row) ? ($row[$col] ?? '') : ($row->$col ?? '');
                    if (in_array($col, $moneyCols)) {
                        $value = number_format($value / 100, 2);
                    }
                    $csvRow[] = $value;
                }
                fputcsv($handle, $csvRow);
            }

            fclose($handle);
        }, "{$reportKey}-{$date}.csv", [
            'Content-Type' => 'text/csv; charset=UTF-8',
        ]);
    }

    private function streamCsv(string $filename, array $headers, $query, callable $rowMapper): StreamedResponse
    {
        $date = now()->format('Y-m-d');

        return response()->streamDownload(function () use ($headers, $query, $rowMapper) {
            $handle = fopen('php://output', 'w');
            fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF));
            fputcsv($handle, $headers);

            foreach ($query->cursor() as $record) {
                fputcsv($handle, $rowMapper($record));
            }

            fclose($handle);
        }, "{$filename}-{$date}.csv", [
            'Content-Type' => 'text/csv; charset=UTF-8',
        ]);
    }
}
