<?php

namespace App\Controllers\View;

use App\Controllers\BaseController;
use App\Libraries\FinancialStatus;
use App\Libraries\InvoiceLedgerService;
use App\Models\AdditionalChargeModel;
use CodeIgniter\Controller;
use App\Models\UserModel;
use App\Models\ConfigurationModel;
use CodeIgniter\Events\Events;
use App\Models\InvoiceModel;
use CodeIgniter\I18n\Time;

class ExtraChargesController extends BaseController
{
    protected $additionalChargeModel;
    protected $db;
    protected $configModel;
    protected $semester;
    protected $schoolYear;
    protected $attendanceDataModel;
    protected $studentModel;
    protected $emergencyContactModel;
    protected $userModel;
    protected $teacherClassSection;
    protected $invoiceModel;
    protected $classSectionModel;
    protected $studentClassModel;
    protected $enableAttendance;
    protected $attendanceDayModel;
    protected $invoiceLedgerService;

    public function __construct()
    {
        // Initialize the database connection in the constructor
        $this->db = \Config\Database::connect();
        $this->configModel = new ConfigurationModel();
        $this->additionalChargeModel = new AdditionalChargeModel();
        $this->userModel = new UserModel();
        $this->invoiceModel = new InvoiceModel();
        $this->semester = $this->configModel->getConfig('semester');
        $this->schoolYear = $this->configModel->getConfig('school_year');
        $this->invoiceLedgerService = new InvoiceLedgerService();
    }

    public function index()
    {
        // API-first: delegate to JSON list endpoint
        return $this->apiList();
    }

    /** Render HTML management page */
    public function page()
    {
        $legacyTermKeys = ['school_year', 'schoolYear', 'year', 'semester'];
        $query = $this->request->getGet();
        $hasLegacyTermFilter = false;
        foreach ($legacyTermKeys as $key) {
            if (array_key_exists($key, $query)) {
                unset($query[$key]);
                $hasLegacyTermFilter = true;
            }
        }
        if ($hasLegacyTermFilter) {
            $target = site_url('admin/charges') . (!empty($query) ? '?' . http_build_query($query) : '');
            return redirect()->to($target);
        }

        $schoolYearContext = $this->resolveSchoolYearContext();
        $schoolYear = $schoolYearContext->yearName();
        $parentId   = (int)($this->request->getGet('parent_id') ?? 0);
        $status     = $this->request->getGet('status') ?: null;

        $rows = [];
        if ($parentId > 0) {
            $rows = $this->additionalChargeModel
                ->byParentTerm($parentId, $schoolYear, $this->semester, $status);
        }

        // Load ALL parents for current view
        $parents = $this->userModel->select('users.id, users.firstname, users.lastname')
            ->join('user_roles', 'user_roles.user_id = users.id')
            ->join('roles', 'roles.id = user_roles.role_id')
            ->where('LOWER(roles.name) =', 'parent')
            ->where('user_roles.deleted_at IS NULL', null, false)
            ->orderBy('users.lastname', 'ASC')
            ->orderBy('users.firstname', 'ASC')
            ->findAll();

        // Map parent id -> "Lastname, Firstname" (no email/ID shown)
        $parentMap = [];
        foreach ($parents as $p) {
            $label = trim(($p['lastname'] ?? '') . ', ' . ($p['firstname'] ?? ''));
            $parentMap[(int)$p['id']] = $label !== '' && $label !== ',' ? $label : ('User #' . (int)$p['id']);
        }

        // 🔹 Get ALL invoices for ALL parents this year, group by parent_id
        $parentIds = array_map(fn($r) => (int)$r['id'], $parents);
        $invoicesByParent = [];
        if (!empty($parentIds)) {
            $all = $this->invoiceModel->getAllInvoicesByUserIds($parentIds, $schoolYear);
            foreach ($all as $inv) {
                $pid = (int)$inv['parent_id'];
                $invoicesByParent[$pid][] = [
                    'id'             => (int)$inv['id'],
                    'invoice_number' => (string)$inv['invoice_number'],
                    'status'         => (string)($inv['status'] ?? ''),
                    'balance'        => (float)($inv['balance'] ?? 0),
                    'issue_date'     => $inv['issue_date'] ?? null,
                ];
            }
        }

        // Label for preselect (if filtering by a parent)
        $selectedParentLabel = ($parentId > 0 && isset($parentMap[$parentId])) ? $parentMap[$parentId] : '';

        // Optional filters
        $status = $this->request->getGet('status') ?: null;
        $q      = trim((string)($this->request->getGet('q') ?? ''));

        // ✅ Always pull all charges for the selected year & current semester (all parents)
        $rows   = $this->additionalChargeModel->listAllForTerm(
            $schoolYear,
            $this->semester,
            $status,
            $q,
            50 // per-page
        );
        $pager  = $this->additionalChargeModel->pager;

        return view('payment/extra_charges', [
            'q'          => $q,
            'pager'      => $pager,
            'rows'                => $rows,
            'parents'             => $parents,
            'parentMap'           => $parentMap,
            'invoicesByParent'    => $invoicesByParent, // 🔹 pass the grouped map
            'parentId'            => $parentId,
            'selectedParentLabel' => $selectedParentLabel,
            'status'              => $status,
            'schoolYear'          => $schoolYear,
            'semester'            => $this->semester,
            'isSchoolYearReadonly' => $schoolYearContext->isReadonly(),
        ]);
    }

    private function wantsJson(): bool
    {
        $accept = strtolower((string)($this->request->getHeaderLine('Accept') ?? ''));
        return $this->request->isAJAX() || str_contains($accept, 'application/json');
    }

    /** Used by Select2 AJAX: returns {results:[{id,text}]} */
    public function parentOptions()
    {
        $q     = trim((string)($this->request->getGet('q') ?? ''));
        $limit = 20;

        $builder = $this->userModel
            ->select('users.id, users.firstname, users.lastname, users.email')
            ->join('user_roles', 'user_roles.user_id = users.id')
            ->join('roles', 'roles.id = user_roles.role_id')
            // If your role name may have different case, use LOWER(...):
            // ->where('LOWER(roles.name) =', 'parent')
            ->where('roles.name', 'parent')
            // Make sure this produces IS NULL; if not, use the raw condition line below
            ->where('user_roles.deleted_at', null);

        // Alternative guaranteed-IS-NULL:
        // $builder->where('user_roles.deleted_at IS NULL', null, false);

        if ($q !== '') {
            $builder->groupStart()
                ->like('users.firstname', $q)
                ->orLike('users.lastname', $q)
                ->orLike('users.email', $q)
                ->groupEnd();
        }

        $rows = $builder
            ->orderBy('users.lastname', 'ASC')
            ->orderBy('users.firstname', 'ASC')
            ->limit($limit)
            ->find();

        $results = array_map(function ($r) {
            $name  = trim(($r['lastname'] ?? '') . ', ' . ($r['firstname'] ?? ''));
            $email = $r['email'] ?? '';
            $label = $name !== ',' ? $name : ('User #' . $r['id']);
            if ($email) $label .= ' — ' . $email;
            $label .= ' (ID: ' . $r['id'] . ')';
            return ['id' => (int)$r['id'], 'text' => $label];
        }, $rows);

        return $this->response->setJSON(['results' => $results]);
    }

    /** Helper for preselect label in the modal */
    private function getParentLabel(int $id): string
    {
        $p = $this->userModel->select('firstname, lastname, email')->find($id);
        if (!$p) return 'User #' . $id;
        $name  = trim(($p['lastname'] ?? '') . ', ' . ($p['firstname'] ?? ''));
        $email = $p['email'] ?? '';
        $label = $name !== ',' ? $name : ('User #' . $id);
        if ($email) $label .= ' — ' . $email;
        return $label . ' (ID: ' . $id . ')';
    }

    public function update($id)
    {
        $row = $this->additionalChargeModel->find($id);
        if (!$row) {
            if ($this->wantsJson()) return $this->response->setJSON(['ok' => false, 'error' => 'Charge not found', 'csrf_token' => csrf_token(), 'csrf_hash' => csrf_hash()]);
            return redirect()->back()->with('error', 'Charge not found.');
        }

        $data = $this->request->getPost();
        $newAmount = isset($data['amount']) ? (float)$data['amount'] : (float)$row['amount'];
        $delta = $newAmount - (float)$row['amount'];

        $db = \Config\Database::connect();
        $db->transStart();

        // Update the charge first
        $this->additionalChargeModel->update($id, [
            'title'       => trim($data['title'] ?? $row['title']),
            'description' => trim($data['description'] ?? $row['description']),
            'amount'      => $newAmount,
            'due_date'    => $data['due_date'] ?? $row['due_date'],
            'charge_type' => $data['charge_type'] ?? $row['charge_type'],
            // keep status as-is
        ]);

        if (($row['status'] ?? '') === FinancialStatus::ADDITIONAL_CHARGE_APPLIED && !empty($row['invoice_id'])) {
            $db->query('SELECT id FROM invoices WHERE id = ? FOR UPDATE', [(int) $row['invoice_id']]);
            $this->invoiceLedgerService->recalculateInvoice((int) $row['invoice_id']);
        }

        $db->transComplete();

        if (!$db->transStatus()) {
            if ($this->wantsJson()) return $this->response->setJSON(['ok' => false, 'error' => 'Failed to update charge', 'csrf_token' => csrf_token(), 'csrf_hash' => csrf_hash()]);
            return redirect()->back()->with('error', 'Failed to update charge.');
        }

        if ($this->wantsJson()) return $this->response->setJSON(['ok' => true, 'id' => (int)$id, 'csrf_token' => csrf_token(), 'csrf_hash' => csrf_hash()]);
        return redirect()->back()->with('status', 'Charge updated.');
    }

    // (removed duplicate model-like listAllForTerm method; use AdditionalChargeModel::listAllForTerm)

    // ExtraChargesController.php
    public function invoicesForParent()
    {
        $parentId   = (int)($this->request->getGet('parent_id') ?? 0);
        $schoolYear = $this->currentSchoolYearName((string)($this->schoolYear ?? ''));

        $rows = ($parentId > 0)
            ? ($this->invoiceModel->getInvoicesByUserId($parentId, $schoolYear) ?? [])
            : [];

        $results = array_map(function ($inv) {
            $id       = (int)($inv['id'] ?? 0);
            $num      = (string)($inv['invoice_number'] ?? ('INV-' . $id));
            $status   = strtolower((string)($inv['status'] ?? ''));
            $balance  = (float)($inv['balance'] ?? 0);
            $issue    = $inv['issue_date'] ?? null;

            return [
                'id'             => $id,
                'invoice_number' => $num,
                'status'         => $status,
                'balance'        => $balance,
                'issue_date'     => $issue,
                'text'           => $num
                    . ($status ? ' — ' . ucfirst($status) : '')
                    . ' (Bal: $' . number_format($balance, 2) . ')',
            ];
        }, $rows);

        return $this->response->setJSON(['results' => $results]);
    }


    public function store()
    {
        $schoolYearContext = $this->resolveSchoolYearContext();
        $this->assertSchoolYearWritable($schoolYearContext);
        $schoolYear = $schoolYearContext->yearName();
        $data = $this->request->getPost();

        $rules = [
            'parent_id'   => 'required|integer',  // this is actually the user_id of the parent
            'title'       => 'required|string|min_length[2]',
            'amount'      => 'required|decimal',
            'charge_type' => 'required|in_list[add,deduct]',
            'due_date'    => 'permit_empty|valid_date',
            'invoice_id'  => 'permit_empty|integer',
        ];
        if (!$this->validate($rules)) {
            $msg = implode(' ', $this->validator->getErrors());
            if ($this->wantsJson()) {
                return $this->response->setJSON([
                    'ok' => false,
                    'error' => $msg,
                    'csrf_token' => csrf_token(),
                    'csrf_hash' => csrf_hash(),
                ]);
            }
            return redirect()->back()->withInput()->with('error', $msg);
        }

        $invoiceId  = !empty($data['invoice_id']) ? (int)$data['invoice_id'] : null;
        $chargeType = (string)$data['charge_type'];

        $amountAbs    = round(abs((float)$data['amount']), 2);
        $signedAmount = ($chargeType === 'add') ? $amountAbs : -$amountAbs;

        $payload = [
            'parent_id'   => (int)$data['parent_id'], // ← users.id of the parent
            'invoice_id'  => $invoiceId,
            'school_year' => $schoolYear,
            'semester'    => (string)$this->semester,
            'charge_type' => $chargeType,
            'title'       => trim($data['title']),
            'description' => trim($data['description'] ?? ''),
            'amount'      => $signedAmount,
            'due_date'    => !empty($data['due_date']) ? $data['due_date'] : null,
            'status'      => $invoiceId ? FinancialStatus::ADDITIONAL_CHARGE_APPLIED : FinancialStatus::ADDITIONAL_CHARGE_PENDING,
            'created_by'  => (int)(session()->get('user_id') ?? 0),
            'created_at'  => \CodeIgniter\I18n\Time::now('UTC')->toDateTimeString(), // store UTC
        ];

        $this->db->transStart();

        // BEFORE
        $invoiceBefore = $this->invoiceModel->getInvoicesByParentId($data['parent_id'], $schoolYear);

        // Insert charge
        $this->additionalChargeModel->insert($payload);
        $chargeId = (int)$this->additionalChargeModel->getInsertID();

        if ($invoiceId) {
            $this->db->query('SELECT id FROM invoices WHERE id = ? FOR UPDATE', [$invoiceId]);
            $this->invoiceLedgerService->recalculateInvoice($invoiceId);
        }

        // AFTER
        $invoiceAfter = $this->invoiceModel->getInvoicesByParentId($data['parent_id'], $schoolYear);

        // Parent USER (not parent table)
        $parentUser = $this->userModel->getUserInfoById($data['parent_id']);

        $this->db->transComplete();

        if (!$this->db->transStatus()) {
            $err = $this->db->error();
            log_message('error', 'Transaction failed in ExtraChargesController::store: ' . json_encode($err));
            if ($this->wantsJson()) {
                return $this->response->setJSON(['ok' => false, 'error' => 'Failed to save charge', 'csrf_token' => csrf_token(), 'csrf_hash' => csrf_hash()]);
            }
            return redirect()->back()->withInput()->with('error', 'Failed to save charge.');
        }

        // Build event payload (same style as your payment handler)
        // allow either first_name/last_name OR firstname/lastname from users table
        $first = $parentUser['first_name'] ?? $parentUser['firstname'] ?? '';
        $last  = $parentUser['last_name']  ?? $parentUser['lastname']  ?? '';
        $parentName = trim($first . ' ' . $last);

        $invoiceBefore = $this->normalizeRow($invoiceBefore);
        $invoiceAfter  = $this->normalizeRow($invoiceAfter);

        $invoiceTotal = $invoiceAfter ? (float)($invoiceAfter['total_amount'] ?? 0) : 0.0;
        $preBalance   = $invoiceBefore ? (float)($invoiceBefore['balance'] ?? 0) : 0.0;
        $postBalance  = $invoiceAfter  ? (float)($invoiceAfter['balance'] ?? 0)  : 0.0;

        $eventData = [
            // user identity (parent user)
            'user_id'        => (int)($parentUser['id'] ?? 0),
            'email'          => $parentUser['email'] ?? null,
            'firstname'      => $first,
            'lastname'       => $last,
            'parentName'     => $parentName,

            // context
            'school_year'    => $payload['school_year'],
            'semester'       => $payload['semester'],

            // invoice
            'invoice_id'     => $invoiceId,
            'invoice_number' => $invoiceAfter['invoice_number'] ?? ($invoiceId ? 'INV-' . $invoiceId : null),

            // charge details
            'charge_id'      => $chargeId,
            'charge_title'   => $payload['title'],
            'charge_desc'    => $payload['description'],
            'charge_type'    => $payload['charge_type'],     // add|deduct
            'amount_signed'  => $signedAmount,
            'amount_abs'     => $amountAbs,
            'due_date'       => $payload['due_date'],
            'created_at'     => $payload['created_at'],

            // money snapshot
            'pre_balance'    => $preBalance,
            'post_balance'   => $postBalance,
            'invoice_total'  => $invoiceTotal,

            // links
            'portal_link'    => base_url('/login'),
            'invoice_link'   => $invoiceId ? site_url('parent/invoices/view/' . $invoiceId) : site_url('parent/invoices'),
        ];

        $students = []; // keep [] unless you want to include children under this user
        Events::trigger('extraCharge', $eventData, $students);

        if ($this->wantsJson()) {
            return $this->response->setJSON([
                'ok'          => true,
                'id'          => $chargeId,
                'invoice_id'  => $invoiceId,
                'parent_id'   => (int)$data['parent_id'],
                'csrf_token'  => csrf_token(),
                'csrf_hash'   => csrf_hash(),
            ]);
        }
        return redirect()->to(site_url('admin/charges'))->with('status', 'Charge recorded.');
    }

    /**
     * Mark a charge as void and roll back its impact on the invoice if applied.
     */
    public function void($id)
    {
        $row = $this->additionalChargeModel->find((int)$id);
        if (!$row) {
            if ($this->wantsJson()) return $this->response->setJSON(['ok' => false, 'error' => 'Charge not found']);
            return redirect()->back()->with('error', 'Charge not found.');
        }

        $invoiceId  = (int)($row['invoice_id'] ?? 0);
        $amountAbs  = round(abs((float)($row['amount'] ?? 0)), 2);
        $chargeType = (string)($row['charge_type'] ?? 'add');
        $status     = (string)($row['status'] ?? 'pending');

        $this->db->transStart();

        $this->additionalChargeModel->update((int)$id, [
            'status' => FinancialStatus::ADDITIONAL_CHARGE_VOIDED,
        ]);

        if ($status === FinancialStatus::ADDITIONAL_CHARGE_APPLIED && $invoiceId > 0) {
            $this->db->query('SELECT id FROM invoices WHERE id = ? FOR UPDATE', [$invoiceId]);
            $this->invoiceLedgerService->recalculateInvoice($invoiceId);
        }

        $this->db->transComplete();

        if (!$this->db->transStatus()) {
            if ($this->wantsJson()) return $this->response->setJSON(['ok' => false, 'error' => 'Failed to void charge']);
            return redirect()->back()->with('error', 'Failed to void charge.');
        }

        if ($this->wantsJson()) return $this->response->setJSON(['ok' => true]);
        return redirect()->back()->with('status', 'Charge voided.');
    }

    /**
     * Reverse a previously applied charge: undo invoice impact and return to pending state.
     */
    public function reverse($id)
    {
        $row = $this->additionalChargeModel->find((int)$id);
        if (!$row) {
            if ($this->wantsJson()) return $this->response->setJSON(['ok' => false, 'error' => 'Charge not found']);
            return redirect()->back()->with('error', 'Charge not found.');
        }

        $invoiceId  = (int)($row['invoice_id'] ?? 0);
        $amountAbs  = round(abs((float)($row['amount'] ?? 0)), 2);
        $chargeType = (string)($row['charge_type'] ?? 'add');
        $status     = (string)($row['status'] ?? 'pending');

        if ($status !== FinancialStatus::ADDITIONAL_CHARGE_APPLIED || $invoiceId <= 0 || $amountAbs <= 0) {
            if ($this->wantsJson()) return $this->response->setJSON(['ok' => false, 'error' => 'Nothing to reverse']);
            return redirect()->back()->with('error', 'Nothing to reverse.');
        }

        $this->db->transStart();
        $this->db->query('SELECT id FROM invoices WHERE id = ? FOR UPDATE', [$invoiceId]);

        $this->additionalChargeModel->update((int)$id, [
            'status'     => FinancialStatus::ADDITIONAL_CHARGE_PENDING,
            'invoice_id' => null,
        ]);
        $this->invoiceLedgerService->recalculateInvoice($invoiceId);

        $this->db->transComplete();

        if (!$this->db->transStatus()) {
            if ($this->wantsJson()) return $this->response->setJSON(['ok' => false, 'error' => 'Failed to reverse charge']);
            return redirect()->back()->with('error', 'Failed to reverse charge.');
        }

        if ($this->wantsJson()) return $this->response->setJSON(['ok' => true]);
        return redirect()->back()->with('status', 'Charge reversed to pending.');
    }

    /** JSON: list charges for the current term (with optional filters). */
    public function apiList()
    {
        $year   = $this->currentSchoolYearName((string)($this->schoolYear ?? ''));
        $sem    = (string)$this->semester;
        $status = $this->request->getGet('status') ?: null;
        $q      = trim((string)($this->request->getGet('q') ?? '')) ?: null;
        $per    = (int)($this->request->getGet('per_page') ?? 50);

        $rows   = $this->additionalChargeModel->listAllForTerm($year, $sem, $status, $q, $per);
        $pager  = $this->additionalChargeModel->pager;

        $meta = [
            'perPage'   => method_exists($pager, 'getPerPage') ? $pager->getPerPage() : $per,
            'page'      => method_exists($pager, 'getCurrentPage') ? $pager->getCurrentPage() : null,
            'total'     => method_exists($pager, 'getTotal') ? $pager->getTotal() : null,
            'pageCount' => method_exists($pager, 'getPageCount') ? $pager->getPageCount() : null,
        ];

        return $this->response->setJSON([
            'ok'          => true,
            'school_year' => $year,
            'semester'    => $sem,
            'rows'        => $rows,
            'pager'       => $meta,
            'csrf_token'  => csrf_token(),
            'csrf_hash'   => csrf_hash(),
        ]);
    }

    // Normalize query result into a single associative row
    private function normalizeRow($row): ?array
    {
        if (!$row) return null;
        if (is_array($row) && isset($row[0]) && is_array($row[0])) {
            return $row[0]; // unwrap first row from a result set
        }
        return is_array($row) ? $row : null;
    }
}
