<?php

namespace App\Models;

use CodeIgniter\Model;

class InvoiceModel extends Model
{
    protected $table      = 'invoices';
    protected $primaryKey = 'id';
    protected $returnType = 'array';

    protected $allowedFields = [
        'parent_id',
        'invoice_number',
        'total_amount',
        'school_year',
        'semester',
        'balance',
        'paid_amount',
        'has_discount',
        'issue_date',   // DATETIME
        'due_date',     // DATETIME (nullable)
        'status',
        'description',
        'created_at',   // DATETIME (DB default/trigger)
        'updated_at',   // DATETIME (DB default/trigger)
        'updated_by',
    ];

    // Your table handles timestamps (defaults/triggers), keep CI auto off
    protected $useTimestamps = false;

    // --- Validate as DATETIME strings like "YYYY-MM-DD HH:MM:SS" ---   
    protected $validationRules = [
    // allow update without tripping uniqueness
    'invoice_number'   => 'required|is_unique[invoices.invoice_number,id,{id}]',
    'total_amount'     => 'required|decimal',
    'balance'          => 'permit_empty|decimal',
    'issue_date'     => 'required|valid_date[Y-m-d H:i:s]',
    'due_date'       => 'permit_empty|valid_date[Y-m-d H:i:s]',
    'status'           => 'required|string|max_length[50]',
];


    protected $validationMessages = [
        'invoice_number' => [
            'required'  => 'The invoice number is required.',
            'is_unique' => 'The invoice number must be unique.',
        ],
        'total_amount' => [
            'required' => 'The total amount is required.',
            'decimal'  => 'The total amount must be a valid decimal.',
        ],
        'balance' => [
            'decimal' => 'The balance must be a valid decimal.',
        ],
        'issue_date' => [
            'required'   => 'The issue date is required.',
            'valid_date' => 'The issue date must be a valid datetime (YYYY-MM-DD HH:MM:SS).',
        ],
        'due_date' => [
            'valid_date' => 'The due date must be a valid datetime (YYYY-MM-DD HH:MM:SS).',
        ],
        'status' => [
            'required'   => 'The status is required.',
            'max_length' => 'The status must not exceed 50 characters.',
        ],
    ];

    // --- Normalize any incoming date/datetime into UTC 'Y-m-d H:i:s' ---
    protected $beforeInsert = ['normalizeDateTimes'];
    protected $beforeUpdate = ['normalizeDateTimes'];

    protected function normalizeDateTimes(array $data): array
    {
        foreach (['issue_date', 'due_date', 'created_at', 'updated_at'] as $field) {
            if (array_key_exists($field, $data['data'])) {
                $val = $data['data'][$field];
                if ($val === null || $val === '' || $val === '0000-00-00' || $val === '0000-00-00 00:00:00') {
                    // keep NULL/empty for nullable fields
                    $data['data'][$field] = ($field === 'due_date') ? null : $val;
                    continue;
                }
                $normalized = $this->toUtcDateTimeString($val);
                if ($normalized !== null) {
                    $data['data'][$field] = $normalized;
                }
                // else leave as-is; validation will catch invalid format
            }
        }
        return $data;
    }

    private function toUtcDateTimeString($raw): ?string
    {
        if ($raw instanceof \DateTimeInterface) {
            $dt = (new \DateTimeImmutable('@' . $raw->getTimestamp()))
                ->setTimezone(new \DateTimeZone('UTC'));
            return $dt->format('Y-m-d H:i:s');
        }

        $s = trim((string) $raw);
        if ($s === '') return null;

        // Try strict formats first
        $utc = new \DateTimeZone('UTC');

        $dt = \DateTime::createFromFormat('Y-m-d H:i:s', $s, $utc);
        if ($dt !== false) {
            $dt->setTimezone($utc);
            return $dt->format('Y-m-d H:i:s');
        }

        $d = \DateTime::createFromFormat('Y-m-d', $s, $utc);
        if ($d !== false) {
            // If date-only sneaks in, store at 12:00:00 to avoid boundary confusion
            $d->setTime(12, 0, 0);
            $d->setTimezone($utc);
            return $d->format('Y-m-d H:i:s');
        }

        // Fallback parser (may accept ISO8601, etc.)
        try {
            $flex = new \DateTime($s);
            $flex->setTimezone($utc);
            return $flex->format('Y-m-d H:i:s');
        } catch (\Throwable $e) {
            return null;
        }
    }

    // ----------------------- Queries -----------------------

    public function getInvoicesByUserId($userId, $schoolYear = null)
    {
        $builder = $this->db->table($this->table . ' i')
            ->select('i.*')
            ->select('(SELECT COALESCE(SUM(du.discount_amount),0) FROM discount_usages du WHERE du.invoice_id = i.id) AS discount', false)
            ->where('i.parent_id', $userId);
        if ($schoolYear !== null) {
            $builder->where('i.school_year', $schoolYear);
        }
        return $builder->get()->getResultArray();
    }

    public function updateInvoiceStatus($invoiceId, $status)
    {
        return $this->update($invoiceId, ['status' => $status]);
    }

    public function getUnpaidInvoices()
    {
        return $this->whereIn('status', ['Unpaid', 'Partially Paid', 'unpaid', 'partially paid'])
            ->orderBy('due_date', 'ASC')
            ->findAll();
    }

    public function getLatestInvoiceTotalAmount($parentId)
    {
        return $this->where('parent_id', $parentId)
            ->orderBy('created_at', 'DESC')
            ->select('total_amount')
            ->get()
            ->getRow('total_amount');
    }

    public function getLatestInvoicePaidAmount($parentId)
    {
        return $this->where('parent_id', $parentId)
            ->orderBy('created_at', 'DESC')
            ->select('paid_amount')
            ->get()
            ->getRow('paid_amount');
    }

    public function getLatestInvoiceBalance($parentId)
    {
        $invoice = $this->where('parent_id', $parentId)
            ->orderBy('created_at', 'DESC')
            ->select('balance')
            ->get()
            ->getRow();

        return $invoice ? $invoice->balance : null;
    }

    public function getInvoiceEventCharges($invoiceId)
    {
        return $this->db->table('invoice_event')
            ->where('invoice_id', $invoiceId)
            ->get()
            ->getResultArray();
    }

    public function getInvoicesByParentId($parentId, $schoolYear = null)
    {
        $builder = $this->db->table($this->table . ' i')
            ->select('i.*')
            ->select('(SELECT COALESCE(SUM(du.discount_amount),0) FROM discount_usages du WHERE du.invoice_id = i.id) AS discount', false)
            ->where('i.parent_id', $parentId);
        if (!empty($schoolYear)) {
            $builder->where('i.school_year', $schoolYear);
        }
        return $builder->orderBy('i.created_at', 'DESC')->get()->getResultArray();
    }

    public function getLatestInvoiceBalanceByYear($parentId, $schoolYear)
    {
        return $this->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->orderBy('created_at', 'DESC')
            ->select('total_amount, paid_amount, balance, updated_at')
            ->get()
            ->getRowArray();
    }

    public function getDiscountedInvoicesByParent($parentId)
    {
        return $this->where('parent_id', $parentId)
            ->where('has_discount', 1)
            ->findAll();
    }

    /* =======================
     * Query helpers
     * ======================= */


    /**
     * Multi-parent variant (useful when preloading for all parents).
     */
    public function getInvoicesByUserIds(array $parentIds, string $schoolYear): array
    {
        if (empty($parentIds)) return [];
        $builder = $this->db->table($this->table . ' i')
            ->select('i.id,i.parent_id,i.invoice_number,i.status,i.balance,i.total_amount,i.issue_date,i.due_date')
            ->select('(SELECT COALESCE(SUM(du.discount_amount),0) FROM discount_usages du WHERE du.invoice_id = i.id) AS discount', false)
            ->whereIn('i.parent_id', array_map('intval', $parentIds))
            ->where('i.school_year', $schoolYear)
            ->orderBy('i.issue_date', 'DESC')
            ->orderBy('i.id', 'DESC');
        return $builder->get()->getResultArray();
    }

    /* =======================
     * Additional charge math
     * ======================= */

    /**
     * Atomically add an extra amount to additional_charge (and optionally totals).
     * Adjust total_amount and balance too (comment out if you don't want that).
     */
public function applyAdditionalCharge(int $invoiceId, float $amount): bool
{
    $val = number_format(abs($amount), 2, '.', '');
    $ok  = $this->db->query(
        "UPDATE {$this->table}
         SET total_amount      = total_amount + {$val},
             balance           = balance + {$val}
         WHERE id = ?", [$invoiceId]
    );
    return $ok && $this->db->affectedRows() > 0;
}

public function deductAdditionalCharge(int $invoiceId, float $amount): bool
{
    $val = number_format(abs($amount), 2, '.', '');
    $ok  = $this->db->query(
        "UPDATE {$this->table}
         SET total_amount      = GREATEST(total_amount - {$val}, 0),
             balance           = GREATEST(balance - {$val}, 0)
         WHERE id = ?", [$invoiceId]
    );
    return $ok && $this->db->affectedRows() > 0;
}


public function getAllInvoicesByUserIds(array $parentIds, string $schoolYear): array
{
    if (empty($parentIds)) return [];
    $parentIds = array_map('intval', $parentIds);
    $builder = $this->db->table($this->table . ' i')
        ->select('i.id, i.parent_id, i.invoice_number, i.status, i.balance, i.issue_date')
        ->select('(SELECT COALESCE(SUM(du.discount_amount),0) FROM discount_usages du WHERE du.invoice_id = i.id) AS discount', false)
        ->whereIn('i.parent_id', $parentIds)
        ->where('i.school_year', $schoolYear)
        ->orderBy('i.issue_date', 'DESC')
        ->orderBy('i.id', 'DESC');
    return $builder->get()->getResultArray();
}



    /**
     * Reverse an extra amount from additional_charge (and totals), never below 0.
     */
    public function reverseAdditionalCharge(int $invoiceId, float $amount): bool
    {
        $amount = round($amount, 2);
        $tb = $this->db->table($this->table);
        $val = $this->db->escape($amount);
        return $tb->set('total_amount',      "GREATEST(total_amount - $val, 0)", false)
            ->set('balance',           "GREATEST(balance - $val, 0)", false)
            ->where('id', $invoiceId)
            ->update();
    }

   
}
