<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\CustomerCreditTransaction;
use App\Models\Expense;
use App\Models\Payment;
use App\Models\Purchase;
use App\Models\ReturnModel;
use App\Models\Sale;
use App\Models\SupplierPayment;
use Illuminate\Http\Request;

class TransactionHistoryController extends Controller
{
    private const VALID_TYPES = [
        'all', 'sale', 'purchase', 'return',
        'expense', 'supplier_payment', 'customer_credit', 'debt_repayment',
    ];

    public function index(Request $request)
    {
        $this->authorize('view-reports');

        $request->validate([
            'start_date' => 'nullable|date',
            'end_date'   => 'nullable|date|after_or_equal:start_date',
            'type'       => 'nullable|string|in:' . implode(',', self::VALID_TYPES),
            'search'     => 'nullable|string|max:100',
            'per_page'   => 'nullable|integer|min:5|max:100',
            'page'       => 'nullable|integer|min:1',
        ]);

        $startDate = $request->input('start_date');
        $endDate   = $request->input('end_date');
        $type      = $request->input('type', 'all');
        $search    = trim((string) $request->input('search', ''));
        $perPage   = (int) $request->input('per_page', 30);
        $page      = max(1, (int) $request->input('page', 1));

        // Cap per-model rows so large datasets don't cause timeouts.
        // 2 000 rows per type × 7 types = 14 000 rows max in PHP memory.
        $rowCap = 2000;

        $transactions = collect();

        // ── 1. Sales (money IN) ────────────────────────────────────────────────
        if (in_array($type, ['all', 'sale'])) {
            $q = Sale::with(['customer:id,name', 'user:id,name']);
            if ($startDate) $q->where('sale_date', '>=', $startDate);
            if ($endDate)   $q->where('sale_date', '<=', $endDate);
            if ($search) {
                $s = $search;
                $q->where(fn($w) =>
                    $w->where('sale_number', 'like', "%{$s}%")
                      ->orWhereHas('customer', fn($w2) => $w2->where('name', 'like', "%{$s}%"))
                );
            }
            $q->orderByDesc('sale_date')->limit($rowCap)->get()->each(function ($sale) use (&$transactions) {
                $transactions->push([
                    'id'          => 'sale_' . $sale->id,
                    'source_id'   => $sale->id,
                    'type'        => 'sale',
                    'reference'   => $sale->sale_number,
                    'date'        => optional($sale->sale_date)->toDateString()
                                     ?? $sale->created_at->toDateString(),
                    'description' => 'Point of Sale',
                    'party'       => $sale->customer?->name ?? 'Walk-in Customer',
                    'amount'      => (float) $sale->total,
                    'direction'   => 'in',
                    'recorded_by' => $sale->user?->name,
                    'status'      => $sale->status,
                    'meta'        => [
                        'amount_paid' => (float) $sale->amount_paid,
                        'outstanding' => (float) $sale->outstanding_balance,
                    ],
                ]);
            });
        }

        // ── 2. Purchases (money OUT) ───────────────────────────────────────────
        if (in_array($type, ['all', 'purchase'])) {
            $q = Purchase::with(['supplier:id,name', 'user:id,name']);
            if ($startDate) $q->where('purchase_date', '>=', $startDate);
            if ($endDate)   $q->where('purchase_date', '<=', $endDate);
            if ($search) {
                $s = $search;
                $q->where(fn($w) =>
                    $w->where('purchase_number', 'like', "%{$s}%")
                      ->orWhereHas('supplier', fn($w2) => $w2->where('name', 'like', "%{$s}%"))
                );
            }
            $q->orderByDesc('purchase_date')->limit($rowCap)->get()->each(function ($purchase) use (&$transactions) {
                $transactions->push([
                    'id'          => 'purchase_' . $purchase->id,
                    'source_id'   => $purchase->id,
                    'type'        => 'purchase',
                    'reference'   => $purchase->purchase_number,
                    'date'        => optional($purchase->purchase_date)->toDateString()
                                     ?? $purchase->created_at->toDateString(),
                    'description' => 'Stock Purchase',
                    'party'       => $purchase->supplier?->name ?? '—',
                    'amount'      => (float) $purchase->total,
                    'direction'   => 'out',
                    'recorded_by' => $purchase->user?->name,
                    'status'      => $purchase->status,
                    'meta'        => [
                        'amount_paid' => (float) $purchase->amount_paid,
                        'amount_due'  => (float) $purchase->amount_due,
                    ],
                ]);
            });
        }

        // ── 3. Returns / Refunds (money OUT) ──────────────────────────────────
        if (in_array($type, ['all', 'return'])) {
            $q = ReturnModel::with([
                'user:id,name',
                'sale:id,sale_number,customer_id',
                'sale.customer:id,name',
            ]);
            if ($startDate) $q->whereDate('created_at', '>=', $startDate);
            if ($endDate)   $q->whereDate('created_at', '<=', $endDate);
            if ($search) {
                $s = $search;
                $q->where(fn($w) =>
                    $w->where('return_number', 'like', "%{$s}%")
                      ->orWhereHas('sale', fn($w2) => $w2->where('sale_number', 'like', "%{$s}%"))
                );
            }
            $q->orderByDesc('created_at')->limit($rowCap)->get()->each(function ($ret) use (&$transactions) {
                $transactions->push([
                    'id'          => 'return_' . $ret->id,
                    'source_id'   => $ret->id,
                    'type'        => 'return',
                    'reference'   => $ret->return_number,
                    'date'        => $ret->created_at->toDateString(),
                    'description' => 'Return / Refund for ' . ($ret->sale?->sale_number ?? '—'),
                    'party'       => $ret->sale?->customer?->name ?? 'Walk-in Customer',
                    'amount'      => (float) $ret->total_amount,
                    'direction'   => 'out',
                    'recorded_by' => $ret->user?->name,
                    'status'      => $ret->status,
                    'meta'        => ['reason' => $ret->reason],
                ]);
            });
        }

        // ── 4. Expenses (money OUT) ────────────────────────────────────────────
        if (in_array($type, ['all', 'expense'])) {
            $q = Expense::with(['user:id,name']);
            if ($startDate) $q->where('expense_date', '>=', $startDate);
            if ($endDate)   $q->where('expense_date', '<=', $endDate);
            if ($search) {
                $s = $search;
                $q->where(fn($w) =>
                    $w->where('description', 'like', "%{$s}%")
                      ->orWhere('category', 'like', "%{$s}%")
                      ->orWhere('expense_number', 'like', "%{$s}%")
                );
            }
            $q->orderByDesc('expense_date')->limit($rowCap)->get()->each(function ($exp) use (&$transactions) {
                $transactions->push([
                    'id'          => 'expense_' . $exp->id,
                    'source_id'   => $exp->id,
                    'type'        => 'expense',
                    'reference'   => $exp->expense_number ?? ('EXP-' . $exp->id),
                    'date'        => optional($exp->expense_date)->toDateString()
                                     ?? $exp->created_at->toDateString(),
                    'description' => $exp->description ?: $exp->category,
                    'party'       => $exp->category ?? '—',
                    'amount'      => (float) $exp->amount,
                    'direction'   => 'out',
                    'recorded_by' => $exp->user?->name,
                    'status'      => 'completed',
                    'meta'        => ['payment_method' => $exp->payment_method],
                ]);
            });
        }

        // ── 5. Supplier Payments (money OUT) ──────────────────────────────────
        if (in_array($type, ['all', 'supplier_payment'])) {
            $q = SupplierPayment::with(['supplier:id,name', 'user:id,name']);
            if ($startDate) $q->where('payment_date', '>=', $startDate);
            if ($endDate)   $q->where('payment_date', '<=', $endDate);
            if ($search) {
                $s = $search;
                $q->where(fn($w) =>
                    $w->where('payment_number', 'like', "%{$s}%")
                      ->orWhereHas('supplier', fn($w2) => $w2->where('name', 'like', "%{$s}%"))
                );
            }
            $q->orderByDesc('payment_date')->limit($rowCap)->get()->each(function ($sp) use (&$transactions) {
                $transactions->push([
                    'id'          => 'supplier_payment_' . $sp->id,
                    'source_id'   => $sp->id,
                    'type'        => 'supplier_payment',
                    'reference'   => $sp->payment_number,
                    'date'        => optional($sp->payment_date)->toDateString()
                                     ?? $sp->created_at->toDateString(),
                    'description' => 'Supplier Payment',
                    'party'       => $sp->supplier?->name ?? '—',
                    'amount'      => (float) $sp->amount,
                    'direction'   => 'out',
                    'recorded_by' => $sp->user?->name,
                    'status'      => 'completed',
                    'meta'        => ['payment_method' => $sp->payment_method],
                ]);
            });
        }

        // ── 6. Customer Credit / Debit Transactions ───────────────────────────
        // 'credit' = business issues credit to customer (liability / money out)
        // 'debit'  = customer uses their credit to pay (money in for the business)
        if (in_array($type, ['all', 'customer_credit'])) {
            $q = CustomerCreditTransaction::with(['customer:id,name', 'user:id,name']);
            if ($startDate) $q->whereDate('created_at', '>=', $startDate);
            if ($endDate)   $q->whereDate('created_at', '<=', $endDate);
            if ($search) {
                $s = $search;
                $q->where(fn($w) =>
                    $w->where('reference', 'like', "%{$s}%")
                      ->orWhereHas('customer', fn($w2) => $w2->where('name', 'like', "%{$s}%"))
                );
            }
            $q->orderByDesc('created_at')->limit($rowCap)->get()->each(function ($cct) use (&$transactions) {
                $isCredit = $cct->type === 'credit';
                $transactions->push([
                    'id'          => 'customer_credit_' . $cct->id,
                    'source_id'   => $cct->id,
                    'type'        => 'customer_credit',
                    'reference'   => $cct->reference ?? ('CC-' . $cct->id),
                    'date'        => $cct->created_at->toDateString(),
                    'description' => $isCredit
                        ? 'Credit issued to customer'
                        : 'Customer credit applied to sale',
                    'party'       => $cct->customer?->name ?? '—',
                    'amount'      => (float) $cct->amount,
                    'direction'   => $isCredit ? 'out' : 'in',
                    'recorded_by' => $cct->user?->name,
                    'status'      => 'completed',
                    'meta'        => [
                        'credit_type' => $cct->type,
                        'notes'       => $cct->notes,
                    ],
                ]);
            });
        }

        // ── 7. Debt Repayments — post-sale payments on outstanding balances ────
        if (in_array($type, ['all', 'debt_repayment'])) {
            $q = Payment::with(['sale:id,sale_number,customer_id', 'sale.customer:id,name']);
            if ($startDate) $q->whereDate('created_at', '>=', $startDate);
            if ($endDate)   $q->whereDate('created_at', '<=', $endDate);
            if ($search) {
                $s = $search;
                $q->where(fn($w) =>
                    $w->where('reference', 'like', "%{$s}%")
                      ->orWhereHas('sale', fn($w2) => $w2->where('sale_number', 'like', "%{$s}%"))
                );
            }
            $q->orderByDesc('created_at')->limit($rowCap)->get()->each(function ($payment) use (&$transactions) {
                $transactions->push([
                    'id'          => 'payment_' . $payment->id,
                    'source_id'   => $payment->id,
                    'type'        => 'debt_repayment',
                    'reference'   => $payment->reference ?? ('PAY-' . $payment->id),
                    'date'        => $payment->created_at->toDateString(),
                    'description' => 'Debt Repayment for ' . ($payment->sale?->sale_number ?? '—'),
                    'party'       => $payment->sale?->customer?->name ?? 'Walk-in Customer',
                    'amount'      => (float) $payment->amount,
                    'direction'   => 'in',
                    'recorded_by' => null,
                    'status'      => 'completed',
                    'meta'        => [
                        'payment_method' => $payment->payment_method,
                        'sale_number'    => $payment->sale?->sale_number,
                    ],
                ]);
            });
        }

        // ── Sort by date descending, then paginate in PHP ──────────────────────
        $sorted = $transactions->sortByDesc('date')->values();
        $total  = $sorted->count();
        $items  = $sorted->slice(($page - 1) * $perPage, $perPage)->values();

        // ── Summary stats (computed across all matching records) ───────────────
        $totalIn  = $transactions->where('direction', 'in')->sum('amount');
        $totalOut = $transactions->where('direction', 'out')->sum('amount');

        return response()->json([
            'data'    => $items,
            'meta'    => [
                'total'        => $total,
                'per_page'     => $perPage,
                'current_page' => $page,
                'last_page'    => max(1, (int) ceil($total / $perPage)),
            ],
            'summary' => [
                'total_in'  => round($totalIn, 2),
                'total_out' => round($totalOut, 2),
                'net'       => round($totalIn - $totalOut, 2),
            ],
        ]);
    }
}
