<?php

declare(strict_types=1);

namespace common\services;

use common\models\Invoice;
use common\models\InvoiceItem;
use common\models\Payment;
use yii\db\Exception;

/**
 * InvoiceService — handles invoice creation, payment recording, and status management.
 */
class InvoiceService
{
    /**
     * Generate the next unique invoice number: INV-YYYY-NNNN
     */
    public function generateInvoiceNumber(): string
    {
        $year = date('Y');
        $prefix = "INV-{$year}-";

        $last = Invoice::find()
            ->select('invoice_number')
            ->where(['LIKE', 'invoice_number', $prefix])
            ->orderBy(['id' => SORT_DESC])
            ->scalar();

        if ($last) {
            $seq = (int)substr($last, strlen($prefix)) + 1;
        } else {
            $seq = 1;
        }

        return $prefix . str_pad((string)$seq, 4, '0', STR_PAD_LEFT);
    }

    /**
     * Create an invoice with line items inside a DB transaction.
     *
     * @param array $invoiceData  Invoice fields
     * @param array $items        Array of ['description', 'quantity', 'unit_price']
     * @throws Exception|\Throwable
     */
    public function createInvoice(array $invoiceData, array $items = []): Invoice
    {
        $transaction = \Yii::$app->db->beginTransaction();

        try {
            $invoice = new Invoice();
            $invoice->invoice_number   = $this->generateInvoiceNumber();
            $invoice->customer_id      = $invoiceData['customer_id'];
            $invoice->customer_plan_id = $invoiceData['customer_plan_id'] ?? null;
            $invoice->invoice_date     = $invoiceData['invoice_date'] ?? date('Y-m-d');
            $invoice->due_date         = $invoiceData['due_date'] ?? null;
            $invoice->gst_rate         = (float)($invoiceData['gst_rate'] ?? 0);
            $invoice->discount_amount  = (float)($invoiceData['discount_amount'] ?? 0);
            $invoice->notes            = $invoiceData['notes'] ?? null;
            $invoice->created_by       = \Yii::$app->user->id ?? null;

            // Calculate totals
            $subtotal = 0.0;
            foreach ($items as $item) {
                $subtotal += (float)$item['quantity'] * (float)$item['unit_price'];
            }

            if (empty($items) && isset($invoiceData['subtotal'])) {
                $subtotal = (float)$invoiceData['subtotal'];
            }

            $invoice->subtotal      = $subtotal;
            $invoice->gst_amount    = round($subtotal * $invoice->gst_rate / 100, 2);
            $invoice->total_amount  = $subtotal + $invoice->gst_amount - $invoice->discount_amount;
            $invoice->paid_amount   = 0;
            $invoice->status        = Invoice::STATUS_UNPAID;

            if (!$invoice->save()) {
                throw new Exception('Invoice save failed: ' . json_encode($invoice->errors));
            }

            // Save line items
            foreach ($items as $sort => $itemData) {
                $item = new InvoiceItem();
                $item->invoice_id   = $invoice->id;
                $item->description  = $itemData['description'];
                $item->quantity     = (float)$itemData['quantity'];
                $item->unit_price   = (float)$itemData['unit_price'];
                $item->total_price  = $item->quantity * $item->unit_price;
                $item->sort_order   = $sort;

                if (!$item->save()) {
                    throw new Exception('Invoice item save failed: ' . json_encode($item->errors));
                }
            }

            $transaction->commit();
            return $invoice;
        } catch (\Throwable $e) {
            $transaction->rollBack();
            throw $e;
        }
    }

    /**
     * Record a payment and update invoice status atomically.
     *
     * @throws Exception|\Throwable
     */
    public function recordPayment(Invoice $invoice, array $paymentData): Payment
    {
        $transaction = \Yii::$app->db->beginTransaction();

        try {
            $amount = (float)$paymentData['amount'];
            $outstanding = $invoice->getOutstandingAmount();

            if ($amount > $outstanding + 0.001) {
                throw new \InvalidArgumentException(
                    "Payment amount ₹{$amount} exceeds outstanding ₹{$outstanding}"
                );
            }

            $payment = new Payment();
            $payment->invoice_id      = $invoice->id;
            $payment->customer_id     = $invoice->customer_id;
            $payment->amount          = $amount;
            $payment->payment_method  = $paymentData['payment_method'] ?? Payment::METHOD_CASH;
            $payment->payment_date    = $paymentData['payment_date'] ?? date('Y-m-d');
            $payment->reference_no    = $paymentData['reference_no'] ?? null;
            $payment->notes           = $paymentData['notes'] ?? null;
            $payment->created_by      = \Yii::$app->user->id ?? null;

            if (!$payment->save()) {
                throw new Exception('Payment save failed: ' . json_encode($payment->errors));
            }

            // Update invoice paid_amount and status
            $newPaid = (float)$invoice->paid_amount + $amount;
            $invoice->paid_amount = $newPaid;

            if ($newPaid >= (float)$invoice->total_amount - 0.001) {
                $invoice->status = Invoice::STATUS_PAID;
            } else {
                $invoice->status = Invoice::STATUS_PARTIAL;
            }

            if (!$invoice->save(false)) {
                throw new Exception('Invoice update failed after payment.');
            }

            $transaction->commit();

            // Invalidate dashboard cache so revenue figures refresh
            \Yii::$app->cache->delete('dashboard_stats');

            return $payment;
        } catch (\Throwable $e) {
            $transaction->rollBack();
            throw $e;
        }
    }

    /**
     * Get revenue summary for dashboard.
     */
    public function getRevenueSummary(): array
    {
        $row = (new \yii\db\Query())
            ->select([
                'SUM(total_amount) AS total_billed',
                'SUM(paid_amount) AS total_collected',
                'SUM(total_amount - paid_amount) AS total_outstanding',
            ])
            ->from('{{%invoices}}')
            ->where(['NOT', ['status' => Invoice::STATUS_CANCELLED]])
            ->one();

        return [
            'total_billed'      => (float)($row['total_billed'] ?? 0),
            'total_collected'   => (float)($row['total_collected'] ?? 0),
            'total_outstanding' => (float)($row['total_outstanding'] ?? 0),
        ];
    }
}
