<?php

namespace App\Services;

use App\Models\Customer;
use App\Models\FinancialYear;
use App\Models\Product;
use App\Models\ProductUnit;
use App\Models\Sale;
use App\Models\Unit;
use App\Models\WarehouseStock;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class SalesService
{
    public function __construct(
        private DocumentSequenceService $sequences,
        private VatCalculationService $vat,
        private FinancialPeriodService $periods,
        private InventoryService $inventory,
        private AccountingService $accounting,
        private AuditLogService $audit,
        private BranchAccessService $branches,
    ) {}

    public function createDraft(array $header, array $items, array $payments = []): Sale
    {
        return DB::transaction(function () use ($header, $items, $payments) {
            $this->branches->assertWarehouse((int) $header['warehouse_id'], $header['branch_id'] ?? null, 'sales');
            if (!$items) {
                throw new RuntimeException('Add at least one product to the invoice.');
            }
            $year = $this->periods->forDate($header['invoice_date']);
            $invoiceType = $header['invoice_type'] ?? 'tax_invoice';
            $isNonTax = $invoiceType === 'non_tax_invoice';
            $sequence = $invoiceType === 'simplified_tax_invoice' ? 'simplified_tax_invoice' : 'sales_invoice';
            $number = $this->sequences->getNextNumber($sequence, $header['branch_id'] ?? null, $year->id)
                ?? throw new RuntimeException('Unable to generate an invoice number.');

            $prepared = [];
            $totals = ['gross' => '0.00', 'discount' => '0.00', 'taxable' => '0.00', 'vat' => '0.00', 'total' => '0.00'];
            foreach ($items as $row) {
                $product = Product::with(['salesUnit', 'baseUnit'])->findOrFail($row['product_id']);
                $unitId = (int) ($row['unit_id'] ?? $product->sales_unit_id ?? $product->base_unit_id);
                $unit = Unit::find($unitId);
                $conversion = ProductUnit::where('product_id', $product->id)->where('unit_id', $unitId)->value('conversion_factor') ?? '1';
                $quantity = $this->vat->number($row['quantity'], 4);
                if (bccomp($quantity, '0', 4) !== 1) {
                    throw new RuntimeException('Invoice quantities must be greater than zero.');
                }
                $calculated = $this->vat->line(
                    $quantity,
                    $row['unit_price'],
                    $isNonTax ? '0' : ($row['vat_rate'] ?? $product->vat_rate),
                    $isNonTax ? 'Exclusive VAT' : ($header['price_mode'] ?? $product->price_mode),
                    $row['discount_type'] ?? 'fixed',
                    $row['discount_value'] ?? '0'
                );
                foreach ($totals as $key => $value) {
                    $totals[$key] = $this->vat->add($value, $calculated[$key], 2);
                }
                $prepared[] = compact('product', 'unitId', 'unit', 'conversion', 'quantity', 'calculated') + ['row' => $row];
            }

            $paymentType = strtolower($header['payment_type'] ?? 'cash');
            if (!$payments && $paymentType !== 'credit') {
                $payments = [['payment_method' => $paymentType, 'amount' => $totals['total']]];
            }
            $paid = '0.00';
            foreach ($payments as $payment) {
                $paid = $this->vat->add($paid, $payment['amount'], 2);
            }
            if (bccomp($paid, $totals['total'], 2) === 1) {
                throw new RuntimeException('Payment total cannot exceed the invoice total.');
            }

            $sale = Sale::create([
                'invoice_no' => $number,
                'invoice_type' => $invoiceType,
                'customer_id' => $header['customer_id'],
                'warehouse_id' => $header['warehouse_id'],
                'branch_id' => $header['branch_id'] ?? null,
                'financial_year_id' => $year->id,
                'van_session_id' => $header['van_session_id'] ?? null,
                'invoice_date' => $header['invoice_date'],
                'invoice_timestamp' => $header['invoice_timestamp'] ?? now(),
                'payment_type' => $paymentType,
                'price_mode' => $isNonTax ? 'Exclusive VAT' : ($header['price_mode'] ?? 'Exclusive VAT'),
                'credit_days' => $header['credit_days'] ?? 0,
                'po_number' => $header['po_number'] ?? null,
                'salesman' => $header['salesman'] ?? null,
                'gross_total' => $totals['gross'],
                'discount_total' => $totals['discount'],
                'taxable_total' => $totals['taxable'],
                'vat_total' => $totals['vat'],
                'grand_total' => $totals['total'],
                'paid_total' => $paid,
                'balance_total' => $this->vat->subtract($totals['total'], $paid),
                'status' => 'draft',
                'notes' => $header['notes'] ?? null,
                'credit_override' => (bool) ($header['credit_override'] ?? false),
                'created_by' => Auth::id(),
            ]);

            foreach ($prepared as $data) {
                $sale->items()->create([
                    'product_id' => $data['product']->id,
                    'unit_id' => $data['unitId'],
                    'product_name_en_snapshot' => $data['product']->name_en,
                    'product_name_ar_snapshot' => $data['product']->name_ar,
                    'unit_snapshot' => $data['unit']?->code,
                    'conversion_factor' => $data['conversion'],
                    'quantity' => $data['quantity'],
                    'base_quantity' => $this->vat->multiply($data['quantity'], $data['conversion'], 4),
                    'unit_price' => $data['row']['unit_price'],
                    'gross_amount' => $data['calculated']['gross'],
                    'discount_amount' => $data['calculated']['discount'],
                    'taxable_amount' => $data['calculated']['taxable'],
                    'vat_rate_snapshot' => $isNonTax ? '0' : ($data['row']['vat_rate'] ?? $data['product']->vat_rate),
                    'vat_amount' => $data['calculated']['vat'],
                    'line_total' => $data['calculated']['total'],
                ]);
            }
            foreach ($payments as $payment) {
                if (bccomp((string) $payment['amount'], '0', 2) === 1) {
                    $sale->payments()->create([
                        'payment_method' => strtolower($payment['payment_method']),
                        'amount' => $payment['amount'],
                        'reference' => $payment['reference'] ?? null,
                    ]);
                }
            }

            $this->audit->log('INVOICE_CREATED', 'sales', $sale->id, null, ['invoice_no' => $sale->invoice_no, 'status' => 'draft']);
            return $sale->load(['items', 'payments']);
        });
    }

    public function finalize(Sale $sale): Sale
    {
        return DB::transaction(function () use ($sale) {
            $sale = Sale::with(['items.product', 'payments', 'customer'])->lockForUpdate()->findOrFail($sale->id);
            if ($sale->status !== 'draft') {
                throw new RuntimeException('Invoice has already been finalized.');
            }
            $this->periods->forDate($sale->invoice_date);
            $this->validateCredit($sale);

            $costTotal = '0.0000';
            foreach ($sale->items as $item) {
                if (!$item->product->track_stock || $item->product->product_type !== 'stock') {
                    continue;
                }
                $stock = WarehouseStock::where('product_id', $item->product_id)
                    ->where('warehouse_id', $sale->warehouse_id)->lockForUpdate()->first();
                $cost = $stock?->avg_cost ?? '0.0000';
                $lineCost = $this->vat->multiply($item->base_quantity, $cost, 4);
                $item->update([
                    'cost_price_snapshot' => $cost,
                    'cost_total' => $lineCost,
                    'gross_profit' => $this->vat->subtract($item->taxable_amount, $lineCost, 2),
                ]);
                $costTotal = $this->vat->add($costTotal, $lineCost, 4);
                $this->inventory->stockOut([
                    'transaction_date' => $sale->invoice_date,
                    'transaction_type' => 'SALE',
                    'reference_type' => 'sales', 'reference_id' => $sale->id,
                    'reference_number' => $sale->invoice_no,
                    'product_id' => $item->product_id, 'warehouse_id' => $sale->warehouse_id,
                    'unit_id' => $item->unit_id, 'qty_out' => $item->quantity,
                    'base_qty_out' => $item->base_quantity, 'unit_cost' => $cost,
                    'allow_negative' => true,
                    'branch_id' => $sale->branch_id, 'financial_year_id' => $sale->financial_year_id,
                ]);
            }

            $company = \App\Models\CompanyProfile::getProfile();
            $sale->cost_total = $costTotal;
            $sale->profit_total = $this->vat->subtract($sale->taxable_total, $costTotal, 2);
            $sale->zatca_qr_tlv = $sale->invoice_type === 'non_tax_invoice'
                ? null
                : ZatcaPhase1QrService::generateTlvBase64(
                    $company->company_name_en,
                    $company->vat_number,
                    $sale->invoice_timestamp->toIso8601String(),
                    $sale->grand_total,
                    $sale->vat_total
                );
            $sale->status = 'finalized';
            $sale->finalized_by = Auth::id();
            $sale->finalized_at = now();
            $sale->save();
            $sale->load('items', 'payments');

            $this->accounting->postSale($sale);
            $this->audit->log('INVOICE_FINALIZED', 'sales', $sale->id, ['status' => 'draft'], ['status' => 'finalized']);
            return $sale;
        });
    }

    private function validateCredit(Sale $sale): void
    {
        if (bccomp((string) $sale->balance_total, '0', 2) !== 1) {
            return;
        }
        $customer = $sale->customer;
        if (!$customer->credit_allowed && !$sale->credit_override) {
            throw new RuntimeException('Credit is not allowed for this customer.');
        }
        if (bccomp((string) $customer->credit_limit, '0', 2) === 1) {
            $after = bcadd((string) $customer->outstanding_balance, (string) $sale->balance_total, 2);
            if (bccomp($after, (string) $customer->credit_limit, 2) === 1 && !$sale->credit_override) {
                throw new RuntimeException('Credit Limit Exceeded. Authorization is required to continue.');
            }
        }
    }
}
