<?php

namespace Licenzo\Services;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

use Licenzo\Model\Invoice;
use Licenzo\Model\InvoiceItem;

class InvoiceService
{
    /**
     * Create a draft invoice with validated required fields.
     *
     * @throws \InvalidArgumentException
     */
    public function createDraft(array $data): Invoice
    {
        $required = ['subscription_id', 'customer_id', 'provider', 'invoice_number'];
        foreach ($required as $key) {
            if (!isset($data[$key]) || $data[$key] === '') {
                throw new \InvalidArgumentException(
                    sprintf('Missing required invoice field: %s', sanitize_key((string) $key))
                );
            }
        }

        $providerInvoiceId = isset($data['provider_invoice_id']) && (string) $data['provider_invoice_id'] !== ''
            ? (string) $data['provider_invoice_id']
            : 'licenzo_inv_' . (string) $data['invoice_number'];

        return Invoice::create([
            'uuid' => $data['uuid'] ?? wp_generate_uuid4(),
            'subscription_id' => (int) $data['subscription_id'],
            'customer_id' => (int) $data['customer_id'],
            'provider' => (string) $data['provider'],
            'provider_invoice_id' => $providerInvoiceId,
            'invoice_number' => (string) $data['invoice_number'],
            'billing_cycle_no' => isset($data['billing_cycle_no']) ? (int) $data['billing_cycle_no'] : 1,
            'installment_index' => isset($data['installment_index']) ? (int) $data['installment_index'] : 1,
            'installment_total' => isset($data['installment_total']) ? (int) $data['installment_total'] : 1,
            'collection_method' => $data['collection_method'] ?? Invoice::COLLECTION_SEND_INVOICE,
            'status' => Invoice::STATUS_DRAFT,
            'currency' => isset($data['currency']) ? strtoupper((string) $data['currency']) : 'USD',
            'subtotal' => isset($data['subtotal']) ? (float) $data['subtotal'] : 0.00,
            'tax_total' => isset($data['tax_total']) ? (float) $data['tax_total'] : 0.00,
            'total' => isset($data['total']) ? (float) $data['total'] : 0.00,
            'amount_due' => isset($data['amount_due']) ? (float) $data['amount_due'] : 0.00,
            'amount_paid' => isset($data['amount_paid']) ? (float) $data['amount_paid'] : 0.00,
            'period_start' => $data['period_start'] ?? null,
            'period_end' => $data['period_end'] ?? null,
            'due_at' => $data['due_at'] ?? null,
            'paid_at' => $data['paid_at'] ?? null,
            'snapshot' => $data['snapshot'] ?? null,
            'meta' => $data['meta'] ?? null,
        ]);
    }

    /**
     * Add or return an invoice line by idempotent tuple.
     *
     * @throws \InvalidArgumentException
     */
    public function addLineItem(int $invoiceId, array $data): InvoiceItem
    {
        if ($invoiceId <= 0) {
            throw new \InvalidArgumentException('Invoice ID is required.');
        }
        if (empty($data['line_type']) || empty($data['reference_type'])) {
            throw new \InvalidArgumentException('line_type and reference_type are required.');
        }

        $referenceId = isset($data['reference_id']) ? (int) $data['reference_id'] : null;

        $existing = InvoiceItem::where('invoice_id', $invoiceId)
            ->where('line_type', (string) $data['line_type'])
            ->where('reference_type', (string) $data['reference_type'])
            ->where('reference_id', $referenceId)
            ->first();

        if ($existing) {
            return $existing;
        }

        $quantity = isset($data['quantity']) ? (float) $data['quantity'] : 1.0;
        $unitAmount = isset($data['unit_amount']) ? (float) $data['unit_amount'] : 0.0;
        $lineTotal = isset($data['line_total']) ? (float) $data['line_total'] : round($quantity * $unitAmount, 2);

        return InvoiceItem::create([
            'invoice_id' => $invoiceId,
            'line_type' => (string) $data['line_type'],
            'reference_type' => (string) $data['reference_type'],
            'reference_id' => $referenceId,
            'description' => $data['description'] ?? null,
            'quantity' => $quantity,
            'unit_amount' => $unitAmount,
            'line_total' => $lineTotal,
            'currency' => isset($data['currency']) ? strtoupper((string) $data['currency']) : 'USD',
            'meta' => $data['meta'] ?? null,
        ]);
    }

    /**
     * Recalculate and persist invoice monetary totals from line items.
     */
    public function recalculateTotals(int $invoiceId): ?Invoice
    {
        $invoice = Invoice::find($invoiceId);
        if (!$invoice) {
            return null;
        }

        $items = InvoiceItem::where('invoice_id', $invoice->id)->get();
        $subtotal = 0.0;
        foreach ($items as $item) {
            $subtotal += (float) $item->line_total;
        }

        $taxTotal = (float) $invoice->tax_total;
        $total = round($subtotal + $taxTotal, 2);
        $amountDue = max(0.0, $total - (float) $invoice->amount_paid);

        $invoice->update([
            'subtotal' => round($subtotal, 2),
            'total' => $total,
            'amount_due' => $amountDue,
        ]);

        return $invoice->fresh();
    }

    /**
     * Mark invoice as paid and set final amounts.
     */
    public function markAsPaid(int $invoiceId, float $amountPaid): ?Invoice
    {
        $invoice = Invoice::find($invoiceId);
        if (!$invoice) {
            return null;
        }

        $nextPaid = (float) $invoice->amount_paid + $amountPaid;
        $nextDue = max(0.0, (float) $invoice->total - $nextPaid);

        $invoice->update([
            'status' => Invoice::STATUS_PAID,
            'amount_paid' => $nextPaid,
            'amount_due' => $nextDue,
            'paid_at' => gmdate('Y-m-d H:i:s'),
        ]);

        return $invoice->fresh();
    }
}
