<?php

namespace App\Models;

use App\Models\BaseModel;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;

class Invoice extends BaseModel
{
    protected $table = 'invoices';

    /**
     * CI: useTimestamps = false because DB defaults/triggers manage created_at/updated_at.
     * In Laravel, keep timestamps OFF and let DB handle them.
     */
    public $timestamps = true;

    protected $fillable = [
        'parent_id',
        'invoice_number',
        'total_amount',
        'balance',
        'paid_amount',
        'has_discount',
        'issue_date',
        'due_date',
        'status',
        'description',
        'school_year',
        'created_at',
        'updated_at',
        'updated_by',
        'semester',
    ];

    protected $casts = [
        'parent_id'    => 'integer',
        'updated_by'   => 'integer',
        'has_discount' => 'boolean',

        'total_amount' => 'decimal:2',
        'balance'      => 'decimal:2',
        'paid_amount'  => 'decimal:2',

        'issue_date'   => 'datetime',
        'due_date'     => 'datetime',
        // created_at/updated_at exist but are DB-managed; you can still cast if you like:
        'created_at'   => 'datetime',
        'updated_at'   => 'datetime',
    ];

    /* Optional relationships */
    public function parent()
    {
        return $this->belongsTo(User::class, 'parent_id');
    }

    public function events()
    {
        return $this->hasMany(InvoiceEvent::class, 'invoice_id');
    }

    public function discountUsages()
    {
        return $this->hasMany(DiscountUsage::class, 'invoice_id');
    }

    /* =========================
     * CI beforeInsert/beforeUpdate equivalents (normalize to UTC)
     * ========================= */

    protected static function booted(): void
    {
        static::creating(function (self $m) {
            $m->normalizeDateTimes();
        });

        static::updating(function (self $m) {
            $m->normalizeDateTimes();
        });
    }

    private function normalizeDateTimes(): void
    {
        foreach (['issue_date', 'due_date', 'created_at', 'updated_at'] as $field) {
            if (!$this->isDirty($field)) continue;

            $val = $this->{$field};

            if ($val === null || $val === '' || $val === '0000-00-00' || $val === '0000-00-00 00:00:00') {
                // keep NULL for due_date; keep other values as-is (DB defaults/triggers)
                if ($field === 'due_date') $this->{$field} = null;
                continue;
            }

            $normalized = static::toUtcDateTimeString($val);
            if ($normalized !== null) {
                $this->{$field} = $normalized;
            }
        }
    }

    private static function toUtcDateTimeString($raw): ?string
    {
        if ($raw instanceof \DateTimeInterface) {
            return Carbon::instance($raw)->utc()->format('Y-m-d H:i:s');
        }

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

        // strict datetime
        try {
            $dt = Carbon::createFromFormat('Y-m-d H:i:s', $s, 'UTC');
            return $dt->utc()->format('Y-m-d H:i:s');
        } catch (\Throwable $e) {
            // ignore
        }

        // date-only sneaks in => store at 12:00:00
        try {
            $d = Carbon::createFromFormat('Y-m-d', $s, 'UTC')->setTime(12, 0, 0);
            return $d->utc()->format('Y-m-d H:i:s');
        } catch (\Throwable $e) {
            // ignore
        }

        // fallback parser (ISO8601 etc.)
        try {
            return Carbon::parse($s)->utc()->format('Y-m-d H:i:s');
        } catch (\Throwable $e) {
            return null;
        }
    }

    /* =========================
     * Queries (ported from CI)
     * ========================= */

    public static function getInvoicesByUserId(int $userId, ?string $schoolYear = null): array
    {
        $q = DB::table('invoices as i')
            ->select('i.*')
            ->selectRaw('(SELECT COALESCE(SUM(du.discount_amount),0) FROM discount_usages du WHERE du.invoice_id = i.id) AS discount')
            ->where('i.parent_id', $userId);

        if ($schoolYear !== null) {
            $q->where('i.school_year', $schoolYear);
        }

        return $q->get()->map(fn ($r) => (array) $r)->all();
    }

    public static function updateInvoiceStatus(int $invoiceId, string $status): bool
    {
        return DB::table('invoices')->where('id', $invoiceId)->update(['status' => $status]) >= 0;
    }

    public static function getUnpaidInvoices(): array
    {
        return DB::table('invoices')
            ->whereIn('status', ['Unpaid', 'Partially Paid', 'unpaid', 'partially paid'])
            ->orderBy('due_date', 'ASC')
            ->get()
            ->map(fn ($r) => (array) $r)
            ->all();
    }

    public static function getLatestInvoiceTotalAmount(int $parentId)
    {
        return DB::table('invoices')
            ->where('parent_id', $parentId)
            ->orderByDesc('created_at')
            ->value('total_amount');
    }

    public static function getLatestInvoicePaidAmount(int $parentId)
    {
        return DB::table('invoices')
            ->where('parent_id', $parentId)
            ->orderByDesc('created_at')
            ->value('paid_amount');
    }

    public static function getLatestInvoiceBalance(int $parentId)
    {
        return DB::table('invoices')
            ->where('parent_id', $parentId)
            ->orderByDesc('created_at')
            ->value('balance');
    }

    public static function getInvoiceEventCharges(int $invoiceId): array
    {
        return DB::table('invoice_event')
            ->where('invoice_id', $invoiceId)
            ->get()
            ->map(fn ($r) => (array) $r)
            ->all();
    }

    public static function getInvoicesByParentId(int $parentId, ?string $schoolYear = null): array
    {
        $q = DB::table('invoices as i')
            ->select('i.*')
            ->selectRaw('(SELECT COALESCE(SUM(du.discount_amount),0) FROM discount_usages du WHERE du.invoice_id = i.id) AS discount')
            ->where('i.parent_id', $parentId);

        if (!empty($schoolYear)) {
            $q->where('i.school_year', $schoolYear);
        }

        return $q->orderByDesc('i.created_at')->get()->map(fn ($r) => (array) $r)->all();
    }

    public static function getLatestInvoiceBalanceByYear(int $parentId, string $schoolYear): ?array
    {
        $row = DB::table('invoices')
            ->where('parent_id', $parentId)
            ->where('school_year', $schoolYear)
            ->orderByDesc('created_at')
            ->select(['total_amount', 'paid_amount', 'balance', 'updated_at'])
            ->first();

        return $row ? (array) $row : null;
    }

    public static function getDiscountedInvoicesByParent(int $parentId): array
    {
        return DB::table('invoices')
            ->where('parent_id', $parentId)
            ->where('has_discount', 1)
            ->get()
            ->map(fn ($r) => (array) $r)
            ->all();
    }

    /**
     * Multi-parent variant (preload invoices for multiple parents).
     */
    public static function getInvoicesByUserIds(array $parentIds, string $schoolYear): array
    {
        $parentIds = array_values(array_unique(array_map('intval', array_filter($parentIds))));
        if (empty($parentIds)) return [];

        return DB::table('invoices as i')
            ->select(['i.id','i.parent_id','i.invoice_number','i.status','i.balance','i.total_amount','i.issue_date','i.due_date'])
            ->selectRaw('(SELECT COALESCE(SUM(du.discount_amount),0) FROM discount_usages du WHERE du.invoice_id = i.id) AS discount')
            ->whereIn('i.parent_id', $parentIds)
            ->where('i.school_year', $schoolYear)
            ->orderByDesc('i.issue_date')
            ->orderByDesc('i.id')
            ->get()
            ->map(fn ($r) => (array) $r)
            ->all();
    }

    public static function getAllInvoicesByUserIds(array $parentIds, string $schoolYear): array
    {
        $parentIds = array_values(array_unique(array_map('intval', array_filter($parentIds))));
        if (empty($parentIds)) return [];

        return DB::table('invoices as i')
            ->select(['i.id','i.parent_id','i.invoice_number','i.status','i.balance','i.issue_date'])
            ->selectRaw('(SELECT COALESCE(SUM(du.discount_amount),0) FROM discount_usages du WHERE du.invoice_id = i.id) AS discount')
            ->whereIn('i.parent_id', $parentIds)
            ->where('i.school_year', $schoolYear)
            ->orderByDesc('i.issue_date')
            ->orderByDesc('i.id')
            ->get()
            ->map(fn ($r) => (array) $r)
            ->all();
    }

    /* =======================
     * Additional charge math (atomic)
     * ======================= */

    public static function applyAdditionalCharge(int $invoiceId, float $amount): bool
    {
        $val = number_format(abs($amount), 2, '.', '');

        $affected = DB::update(
            "UPDATE invoices
             SET total_amount = total_amount + {$val},
                 balance      = balance + {$val}
             WHERE id = ?",
            [$invoiceId]
        );

        return $affected > 0;
    }

    public static function deductAdditionalCharge(int $invoiceId, float $amount): bool
    {
        $val = number_format(abs($amount), 2, '.', '');

        $affected = DB::update(
            "UPDATE invoices
             SET total_amount = GREATEST(total_amount - {$val}, 0),
                 balance      = GREATEST(balance - {$val}, 0)
             WHERE id = ?",
            [$invoiceId]
        );

        return $affected > 0;
    }

    public static function reverseAdditionalCharge(int $invoiceId, float $amount): bool
    {
        $amount = round($amount, 2);

        $affected = DB::table('invoices')
            ->where('id', $invoiceId)
            ->update([
                'total_amount' => DB::raw('GREATEST(total_amount - ' . $amount . ', 0)'),
                'balance'      => DB::raw('GREATEST(balance - ' . $amount . ', 0)'),
            ]);

        return $affected >= 0;
    }
}