<?php

namespace App\Services\Reimbursements;

use App\Models\Expense;
use App\Models\ReimbursementBatchAdminFile;
use Illuminate\Support\Facades\DB;

class ReimbursementQueryService
{
    public function __construct(
        private ReimbursementRecipientService $recipients,
        private ReimbursementFileService $files
    ) {
    }

    public function underProcessing(): array
    {
        $pendingQuery = DB::table('expenses as e')
            ->selectRaw('e.id as expense_id, e.amount as expense_amount, e.description, e.receipt_path as expense_receipt, e.retailor, e.purchased_by, u.firstname as purchaser_firstname, u.lastname as purchaser_lastname')
            ->leftJoin('users as u', 'u.id', '=', 'e.purchased_by')
            ->leftJoin('reimbursement_batch_items as bi', function ($join) {
                $join->on('bi.expense_id', '=', 'e.id');
                $join->whereNull('bi.unassigned_at');
            })
            ->whereNull('e.reimbursement_id')
            ->where('e.category', '!=', 'Donation')
            ->where(function ($q) {
                $q->where('e.status', 'approved')
                    ->orWhere('e.status', 'Approved')
                    ->orWhere('e.status', 'APPROVED');
            })
            ->whereNull('bi.id')
            ->orderByDesc('e.created_at');

        $pendingRows = $pendingQuery->get();

        $pendingItems = [];
        $itemsMap = [];

        foreach ($pendingRows as $row) {
            $expenseId = (int) ($row->expense_id ?? 0);
            if ($expenseId <= 0) {
                continue;
            }

            $receiptUrl = $this->files->expenseReceiptUrl($row->expense_receipt ?? null);
            $fullName = trim(($row->purchaser_firstname ?? '') . ' ' . ($row->purchaser_lastname ?? '')) ?: 'Unknown';

            $item = [
                'id' => $expenseId,
                'expense_id' => $expenseId,
                'reimbursement_id' => null,
                'expense_amount' => (float) ($row->expense_amount ?? 0),
                'vendor' => (string) ($row->retailor ?? ''),
                'description' => trim((string) ($row->description ?? '')),
                'purchased_by' => $fullName,
                'receipt_url' => $receiptUrl,
                'batch_id' => null,
                'batch_label' => null,
                'admin_id' => null,
                'admin_name' => null,
                'batch_number' => 0,
            ];

            $pendingItems[] = $item;
            $itemsMap[$expenseId] = $item;
        }

        $assignedQuery = DB::table('reimbursement_batch_items as bi')
            ->selectRaw('bi.id as batch_item_id, bi.batch_id, bi.admin_id, bi.reimbursement_id, bi.expense_id, b.title as batch_title, b.status as batch_status, b.yearly_batch_number as batch_year_number, e.amount as expense_amount, e.description, e.receipt_path as expense_receipt, e.retailor, purchaser.firstname as purchaser_firstname, purchaser.lastname as purchaser_lastname, admin.firstname as admin_firstname, admin.lastname as admin_lastname')
            ->join('reimbursement_batches as b', 'b.id', '=', 'bi.batch_id')
            ->join('expenses as e', 'e.id', '=', 'bi.expense_id')
            ->leftJoin('users as purchaser', 'purchaser.id', '=', 'e.purchased_by')
            ->leftJoin('users as admin', 'admin.id', '=', 'bi.admin_id')
            ->where('b.status', 'open')
            ->whereNull('bi.unassigned_at')
            ->whereNull('e.reimbursement_id')
            ->where('e.category', '!=', 'Donation')
            ->where(function ($q) {
                $q->where('e.status', 'approved')
                    ->orWhere('e.status', 'Approved')
                    ->orWhere('e.status', 'APPROVED');
            })
            ->orderBy('b.opened_at', 'ASC')
            ->orderBy('bi.assigned_at', 'ASC');

        $assignedRows = $assignedQuery->get();
        $batchStructures = [];

        foreach ($assignedRows as $row) {
            $expenseId = (int) ($row->expense_id ?? 0);
            $batchId = (int) ($row->batch_id ?? 0);
            if ($expenseId <= 0 || $batchId <= 0) {
                continue;
            }

            $receiptUrl = $this->files->expenseReceiptUrl($row->expense_receipt ?? null);
            $purchaser = trim(($row->purchaser_firstname ?? '') . ' ' . ($row->purchaser_lastname ?? '')) ?: 'Unknown';
            $yearlyNumber = (int) ($row->batch_year_number ?? 0);
            $batchLabel = trim((string) ($row->batch_title ?? ''));
            if ($batchLabel === '') {
                $fallbackNumber = $yearlyNumber > 0 ? $yearlyNumber : $batchId;
                $batchLabel = 'Batch #' . $fallbackNumber;
            }
            $adminId = $row->admin_id ? (int) $row->admin_id : 0;
            $adminName = $adminId > 0
                ? (trim(($row->admin_firstname ?? '') . ' ' . ($row->admin_lastname ?? '')) ?: 'Admin')
                : 'Unassigned';
            $batchStatus = strtolower(trim((string) ($row->batch_status ?? ''))) ?: 'open';

            $item = [
                'id' => $expenseId,
                'expense_id' => $expenseId,
                'reimbursement_id' => $row->reimbursement_id ? (int) $row->reimbursement_id : null,
                'expense_amount' => (float) ($row->expense_amount ?? 0),
                'vendor' => (string) ($row->retailor ?? ''),
                'description' => trim((string) ($row->description ?? '')),
                'purchased_by' => $purchaser,
                'receipt_url' => $receiptUrl,
                'batch_id' => $batchId,
                'batch_label' => $batchLabel,
                'admin_id' => $adminId ?: null,
                'admin_name' => $adminId > 0 ? $adminName : null,
                'batch_number' => $batchId,
            ];

            $itemsMap[$expenseId] = $item;

            if (!isset($batchStructures[$batchId])) {
                $batchStructures[$batchId] = [
                    'batchId' => $batchId,
                    'label' => $batchLabel,
                    'slots' => [],
                    'status' => $batchStatus,
                    'yearly_batch_number' => $yearlyNumber,
                ];
            } else {
                $batchStructures[$batchId]['status'] = $batchStatus;
                if ($yearlyNumber > 0) {
                    $batchStructures[$batchId]['yearly_batch_number'] = $yearlyNumber;
                }
            }

            $slotKey = $adminId ?: 0;
            if (!isset($batchStructures[$batchId]['slots'][$slotKey])) {
                $batchStructures[$batchId]['slots'][$slotKey] = [
                    'admin_id' => $adminId ?: 0,
                    'admin_name' => $adminId > 0 ? $adminName : 'Unassigned',
                    'items' => [],
                    'check_file' => null,
                ];
            }
            $batchStructures[$batchId]['slots'][$slotKey]['items'][] = $expenseId;
        }

        $adminFileMap = [];
        if (!empty($batchStructures)) {
            $batchIds = array_keys($batchStructures);
            $files = ReimbursementBatchAdminFile::query()
                ->select('batch_id', 'admin_id', 'filename', 'original_filename')
                ->whereIn('batch_id', $batchIds)
                ->get();

            foreach ($files as $file) {
                $batchId = (int) ($file->batch_id ?? 0);
                $adminId = (int) ($file->admin_id ?? 0);
                if ($batchId <= 0) {
                    continue;
                }
                $adminFileMap[$batchId][$adminId] = [
                    'filename' => $file->filename,
                    'original_filename' => $file->original_filename ?? null,
                    'url' => $this->files->adminFileUrl($file->filename ?? '', 'inline'),
                ];
            }
        }

        foreach ($batchStructures as $batchId => &$batch) {
            foreach ($batch['slots'] as $slotKey => &$slot) {
                $adminId = $slot['admin_id'] ?? 0;
                $slot['check_file'] = $adminFileMap[$batchId][$adminId] ?? null;
            }
            unset($slot);
        }
        unset($batch);

        $existingBatches = array_map(static function (array $batch) {
            $normalizedSlots = array_map(static function (array $slot) {
                $slot['items'] = array_values(array_map('intval', $slot['items']));
                return $slot;
            }, array_values($batch['slots']));

            $itemIds = [];
            foreach ($normalizedSlots as $slot) {
                $itemIds = array_merge($itemIds, $slot['items']);
            }

            return [
                'batchId' => $batch['batchId'],
                'batchNumber' => $batch['batchId'],
                'label' => $batch['label'],
                'sequence' => $batch['yearly_batch_number'] ?? 0,
                'yearly_batch_number' => $batch['yearly_batch_number'] ?? 0,
                'slots' => $normalizedSlots,
                'itemIds' => array_values(array_unique($itemIds)),
                'status' => $batch['status'] ?? 'open',
            ];
        }, array_values($batchStructures));

        $admins = $this->recipients->adminRoster();

        return [
            'pendingItems' => $pendingItems,
            'existingBatches' => $existingBatches,
            'adminUsers' => $admins,
            'itemsPayload' => array_values($itemsMap),
        ];
    }

    public function index(array $filters): array
    {
        $expenses = Expense::getReimbursedExpensesWithDetails($filters)
            ->get()
            ->map(static fn ($row) => (array) $row)
            ->all();

        $batchSummaries = [];
        $batchDetails = [];
        $batchAttachments = [];

        $batchQuery = DB::table('reimbursement_batches as b')
            ->selectRaw('b.id as batch_id, b.title as batch_title, b.yearly_batch_number, b.closed_at, b.school_year as batch_school_year, e.id as expense_id, e.amount as expense_amount, e.description, e.retailor, e.receipt_path as expense_receipt, e.date_of_purchase as expense_date_of_purchase, e.purchased_by, u.firstname as purchaser_firstname, u.lastname as purchaser_lastname, r.amount as reimb_amount')
            ->join('reimbursement_batch_items as bi', 'bi.batch_id', '=', 'b.id')
            ->join('expenses as e', 'e.id', '=', 'bi.expense_id')
            ->leftJoin('users as u', 'u.id', '=', 'e.purchased_by')
            ->leftJoin('reimbursements as r', 'r.expense_id', '=', 'e.id')
            ->where('b.status', 'closed')
            ->whereNull('bi.unassigned_at');

        if (!empty($filters['school_year'])) {
            $batchQuery->where(function ($q) use ($filters) {
                $q->where('b.school_year', $filters['school_year'])
                    ->orWhere('e.school_year', $filters['school_year']);
            });
        }
        if (!empty($filters['user_id'])) {
            $batchQuery->where('e.purchased_by', $filters['user_id']);
        }

        $batchRows = $batchQuery->get();
        foreach ($batchRows as $row) {
            $bid = (int) ($row->batch_id ?? 0);
            if ($bid <= 0) {
                continue;
            }
            $labelNumber = (int) ($row->yearly_batch_number ?? $bid);
            if (!isset($batchSummaries[$bid])) {
                $batchSummaries[$bid] = [
                    'batch_id' => $bid,
                    'title' => trim((string) ($row->batch_title ?? '')) ?: ('Batch #' . $labelNumber),
                    'sequence' => $labelNumber,
                    'closed_at' => $row->closed_at ?? null,
                    'amount' => 0.0,
                    'items' => 0,
                ];
            }
            $amount = $row->reimb_amount ?? $row->expense_amount ?? 0;
            $batchSummaries[$bid]['amount'] += (float) $amount;
            $batchSummaries[$bid]['items'] += 1;

            if (!isset($batchDetails[$bid])) {
                $batchDetails[$bid] = [
                    'title' => $batchSummaries[$bid]['title'],
                    'sequence' => $labelNumber,
                    'closed_at' => $batchSummaries[$bid]['closed_at'],
                    'items' => [],
                    'checks' => [],
                ];
            }
            if (!isset($batchAttachments[$bid])) {
                $batchAttachments[$bid] = [
                    'receipts' => [],
                    'checks' => [],
                ];
            }

            $batchDetails[$bid]['items'][] = [
                'amount' => (float) $amount,
                'description' => $row->description ?? '',
                'vendor' => $row->retailor ?? '',
                'purchased_by' => trim(($row->purchaser_firstname ?? '') . ' ' . ($row->purchaser_lastname ?? '')),
                'receipt_url' => $this->files->expenseReceiptUrl($row->expense_receipt ?? null),
            ];

            $receiptFilename = trim((string) ($row->expense_receipt ?? ''));
            if ($receiptFilename !== '') {
                $batchAttachments[$bid]['receipts'][] = [
                    'expense_id' => (int) ($row->expense_id ?? 0),
                    'receipt_filename' => $receiptFilename,
                    'receipt_url' => $this->files->expenseReceiptUrl($receiptFilename),
                    'purchaser_firstname' => $row->purchaser_firstname ?? '',
                    'purchaser_lastname' => $row->purchaser_lastname ?? '',
                    'vendor' => $row->retailor ?? '',
                    'description' => $row->description ?? '',
                    'amount' => (float) ($row->expense_amount ?? 0),
                    'date_of_purchase' => $row->expense_date_of_purchase ?? '',
                ];
            }
        }

        if (!empty($batchSummaries)) {
            $batchIds = array_keys($batchSummaries);
            $files = ReimbursementBatchAdminFile::query()
                ->select('id as file_id', 'batch_id', 'admin_id', 'filename', 'original_filename', 'uploaded_at')
                ->whereIn('batch_id', $batchIds)
                ->get();

            $adminNames = $this->recipients->adminRoster();
            $adminNameMap = [];
            foreach ($adminNames as $admin) {
                $adminNameMap[(int) $admin['id']] = trim(($admin['firstname'] ?? '') . ' ' . ($admin['lastname'] ?? ''));
            }

            foreach ($files as $file) {
                $bid = (int) ($file->batch_id ?? 0);
                if ($bid <= 0 || !isset($batchDetails[$bid])) {
                    continue;
                }
                $aid = (int) ($file->admin_id ?? 0);
                $batchDetails[$bid]['checks'][] = [
                    'admin' => $adminNameMap[$aid] ?? ($aid > 0 ? 'Admin #' . $aid : 'Unassigned'),
                    'filename' => $file->filename,
                    'original' => $file->original_filename ?? $file->filename,
                    'url' => $this->files->adminFileUrl($file->filename ?? '', 'inline'),
                ];
                if (!isset($batchAttachments[$bid])) {
                    $batchAttachments[$bid] = [
                        'receipts' => [],
                        'checks' => [],
                    ];
                }
                $batchAttachments[$bid]['checks'][] = [
                    'file_id' => (int) ($file->file_id ?? 0),
                    'filename' => $file->filename,
                    'original_filename' => $file->original_filename ?? $file->filename,
                    'admin' => $adminNameMap[$aid] ?? ($aid > 0 ? 'Admin #' . $aid : 'Unassigned'),
                    'url' => $this->files->adminFileUrl($file->filename ?? '', 'inline'),
                    'uploaded_at' => $file->uploaded_at ?? null,
                ];
            }
        }

        if (!empty($batchSummaries)) {
            $batchSummaries = array_values($batchSummaries);
            usort($batchSummaries, static function ($a, $b) {
                $aSeq = (int) ($a['sequence'] ?? 0);
                $bSeq = (int) ($b['sequence'] ?? 0);
                return $bSeq <=> $aSeq;
            });
        }

        $donationBatch = null;
        $donationDetails = [
            'items' => [],
            'checks' => [],
        ];

        $donationQuery = DB::table('expenses as e')
            ->selectRaw('e.id as expense_id, e.amount as expense_amount, e.description, e.retailor, e.receipt_path as expense_receipt, e.purchased_by, e.school_year, e.semester, e.status, u.firstname as purchaser_firstname, u.lastname as purchaser_lastname')
            ->leftJoin('users as u', 'u.id', '=', 'e.purchased_by')
            ->where('e.category', 'Donation');

        if (!empty($filters['school_year'])) {
            $donationQuery->where('e.school_year', $filters['school_year']);
        }
        if (!empty($filters['semester'])) {
            $donationQuery->where('e.semester', $filters['semester']);
        }
        if (!empty($filters['user_id'])) {
            $donationQuery->where('e.purchased_by', $filters['user_id']);
        }
        if (!empty($filters['status'])) {
            $donationQuery->where(function ($q) use ($filters) {
                $q->where('e.status', $filters['status'])
                    ->orWhere('e.status', strtolower((string) $filters['status']))
                    ->orWhere('e.status', strtoupper((string) $filters['status']));
            });
        }

        $donationRows = $donationQuery->orderByDesc('e.created_at')->get();
        if ($donationRows->isNotEmpty()) {
            $donationTotal = 0.0;
            foreach ($donationRows as $row) {
                $amount = (float) ($row->expense_amount ?? 0);
                $donationTotal += $amount;
                $purchaser = trim(($row->purchaser_firstname ?? '') . ' ' . ($row->purchaser_lastname ?? ''));
                if ($purchaser === '') {
                    $purchaser = 'Unknown';
                }
                $donationDetails['items'][] = [
                    'amount' => $amount,
                    'description' => $row->description ?? '',
                    'vendor' => $row->retailor ?? '',
                    'purchased_by' => $purchaser,
                    'receipt_url' => $this->files->expenseReceiptUrl($row->expense_receipt ?? null),
                ];
            }

            $donationBatch = [
                'title' => 'Donations',
                'items' => count($donationDetails['items']),
                'amount' => $donationTotal,
            ];
        }

        foreach ($expenses as &$e) {
            if (array_key_exists('expense_receipt', $e)) {
                $e['expense_receipt_url'] = $this->files->expenseReceiptUrl($e['expense_receipt']);
            } elseif (array_key_exists('receipt_path', $e)) {
                $e['expense_receipt_url'] = $this->files->expenseReceiptUrl($e['receipt_path']);
            }

            if (array_key_exists('reimb_receipt', $e)) {
                $e['reimb_receipt_url'] = $this->files->receiptUrl($e['reimb_receipt']);
            } elseif (array_key_exists('receipt_path_reimb', $e)) {
                $e['reimb_receipt_url'] = $this->files->receiptUrl($e['receipt_path_reimb']);
            }

            $recipientId = $e['reimb_recipient_id'] ?? ($e['reimbursed_to'] ?? null);
            $hasName = trim(($e['reimb_firstname'] ?? '') . ' ' . ($e['reimb_lastname'] ?? '')) !== '';
            if (!$hasName) {
                $label = $this->recipients->specialRecipientLabel($recipientId);
                if ($label !== null) {
                    $e['reimb_firstname'] = $label;
                    $e['reimb_lastname'] = '';
                }
            }
        }
        unset($e);

        $users = $this->recipients->recipientOptions();

        $years = DB::table('reimbursements')
            ->select('school_year')
            ->distinct()
            ->orderByDesc('school_year')
            ->get();
        $schoolYears = array_values(array_filter(array_map(static fn ($row) => $row->school_year ?? null, $years->all())));

        return [
            'expenses' => $expenses,
            'users' => $users,
            'schoolYears' => $schoolYears,
            'batchSummaries' => $batchSummaries,
            'batchDetails' => $batchDetails,
            'batchAttachments' => $batchAttachments,
            'donationBatch' => $donationBatch,
            'donationDetails' => $donationDetails,
        ];
    }

    public function reimbursedExpenses(): array
    {
        $rows = DB::table('reimbursements as r')
            ->selectRaw('e.amount as expense_amount, e.category, e.description, e.receipt_path as expense_receipt, e.purchased_by, r.amount as reimb_amount, r.reimbursement_method, r.check_number, r.receipt_path as reimb_receipt, r.status as reimb_status, r.created_at, u.firstname as purchaser_firstname, u.lastname as purchaser_lastname, a.firstname as approver_firstname, a.lastname as approver_lastname')
            ->join('expenses as e', 'e.id', '=', 'r.expense_id')
            ->leftJoin('users as u', 'u.id', '=', 'e.purchased_by')
            ->leftJoin('users as a', 'a.id', '=', 'r.approved_by')
            ->orderByDesc('r.created_at')
            ->get();

        $expenses = [];
        foreach ($rows as $row) {
            $expenses[] = [
                'expense_amount' => (float) ($row->expense_amount ?? 0),
                'category' => $row->category ?? null,
                'description' => $row->description ?? null,
                'expense_receipt' => $row->expense_receipt ?? null,
                'purchased_by' => $row->purchased_by ?? null,
                'reimb_amount' => (float) ($row->reimb_amount ?? 0),
                'reimbursement_method' => $row->reimbursement_method ?? null,
                'check_number' => $row->check_number ?? null,
                'reimb_receipt' => $row->reimb_receipt ?? null,
                'reimb_status' => $row->reimb_status ?? null,
                'created_at' => $row->created_at ?? null,
                'purchaser_firstname' => $row->purchaser_firstname ?? null,
                'purchaser_lastname' => $row->purchaser_lastname ?? null,
                'approver_firstname' => $row->approver_firstname ?? null,
                'approver_lastname' => $row->approver_lastname ?? null,
                'expense_receipt_url' => $this->files->expenseReceiptUrl($row->expense_receipt ?? null),
                'reimb_receipt_url' => $this->files->receiptUrl($row->reimb_receipt ?? null),
            ];
        }

        return $expenses;
    }
}
