<?php

namespace App\Http\Controllers;

use App\Exports\RevenueByProductExport;
use App\Exports\RevenueByTransactionExport;
use App\Models\Order;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Facades\Excel;

class ReportRevenueController extends Controller
{
    public function export(Request $request)
    {
        $tab = $request->get('tab', 'transaksi');

        $start = $request->filled('start_date')
            ? Carbon::parse($request->start_date)->startOfDay()
            : now()->startOfMonth()->startOfDay();

        $end = $request->filled('end_date')
            ? Carbon::parse($request->end_date)->endOfDay()
            : now()->endOfDay();

        $ordersQuery = Order::query()
            ->with($this->getExportOrderRelations())
            ->where('is_cancelled', false)
            ->whereHas('payments', function ($q) use ($start, $end) {
                $q->whereBetween('created_at', [$start, $end]);
            });

        $this->applyMachineFilterIfAvailable($ordersQuery, $request);

        if ($request->filled('category_id')) {
            $ordersQuery->whereHas('items.material', function ($q) use ($request) {
                $q->where('category_id', $request->category_id);
            });
        }

        $orders = $ordersQuery
            ->get()
            ->map(function ($order) use ($request) {
                return $this->decorateOrderForExport($order, $request);
            })
            ->values();

        if ($tab === 'transaksi') {
            $paymentRows = $this->buildRevenueByPaymentRows($orders, $start, $end);

            $summary = [
                'total_revenue' => (float) $paymentRows->sum(fn ($row) => (float) ($row->net_amount ?? $row->amount ?? 0)),
                'total_paid' => (float) $paymentRows->sum(fn ($row) => (float) ($row->paid_amount ?? 0)),
                'total_change' => (float) $paymentRows->sum(fn ($row) => (float) ($row->change_amount ?? 0)),
                'total_transactions' => (int) $paymentRows->count(),
            ];

            $filename = 'laporan_pendapatan_transaksi_' . $start->format('Ymd') . '_' . $end->format('Ymd') . '.xlsx';

            return Excel::download(
                new RevenueByTransactionExport($paymentRows, $summary),
                $filename
            );
        }

        $byProduct = $this->buildRevenueByProductFromPayments($orders, $start, $end);

        $summary = [
            'total_revenue' => (float) $byProduct->sum('total_revenue'),
        ];

        $filename = 'laporan_pendapatan_produk_' . $start->format('Ymd') . '_' . $end->format('Ymd') . '.xlsx';

        return Excel::download(
            new RevenueByProductExport($byProduct, $summary),
            $filename
        );
    }

    private function getExportOrderRelations(): array
    {
        $relations = [
            'payments',
            'payments.cashier',
            'payments.paymentAccount',
            'items',
            'items.material',
            'customer',
            'designer',
            'cashier',
        ];

        if ($this->hasMaterialMachinesRelation()) {
            $relations[] = 'items.material.machines';
        }

        return $relations;
    }

    private function hasMaterialMachinesRelation(): bool
    {
        return method_exists(\App\Models\Material::class, 'machines');
    }

    private function applyMachineFilterIfAvailable($query, Request $request): void
    {
        if (!$request->filled('machine_id')) {
            return;
        }

        if (!$this->hasMaterialMachinesRelation()) {
            return;
        }

        $query->whereHas('items.material.machines', function ($q) use ($request) {
            $q->where('machines.id', $request->machine_id);
        });
    }

    private function normalizeUnit(?string $unit): string
    {
        $unit = strtolower(trim((string) $unit));

        return match ($unit) {
            'm²', 'm2' => 'm2',
            'm', 'meter' => 'meter',
            'pcs', 'pc', 'piece', 'lembar' => 'lembar',
            default => $unit !== '' ? $unit : 'lembar',
        };
    }

    private function parseM2FromDescription(?string $description): float
    {
        $description = strtolower(trim((string) $description));

        if ($description === '') {
            return 1;
        }

        $description = str_replace(['×', '*'], 'x', $description);

        if (!preg_match('/^\s*(\d+(?:\.\d+)?)\s*x\s*(\d+(?:\.\d+)?)\s*$/', $description, $matches)) {
            return 1;
        }

        $a = (float) $matches[1];
        $b = (float) $matches[2];

        if ($a >= 10 || $b >= 10) {
            $a = $a / 100;
            $b = $b / 100;
        }

        return round($a * $b, 4);
    }

    private function resolveItemSizeValue($item): float
    {
        $unit = $this->normalizeUnit($item->size_unit ?? 'lembar');
        $sizeValue = (float) ($item->size_value ?? 0);

        if ($unit === 'm2') {
            if ($sizeValue <= 0) {
                $sizeValue = $this->parseM2FromDescription($item->size_description ?? '');
            }
        } elseif ($sizeValue <= 0) {
            $sizeValue = 1;
        }

        return $sizeValue > 0 ? $sizeValue : 1;
    }

    private function calculateItemSubtotal($item): float
    {
        $unit = $this->normalizeUnit($item->size_unit ?? 'lembar');
        $qty = max((float) ($item->qty ?? 0), 0);
        $basePrice = max((float) ($item->base_price ?? 0), 0);
        $storedSubtotal = max((float) ($item->subtotal ?? 0), 0);
        $sizeValue = $this->resolveItemSizeValue($item);

        if ($unit === 'm2') {
            return round($basePrice * $sizeValue * $qty, 2);
        }

        if ($unit === 'meter') {
            return $storedSubtotal > 0
                ? round($storedSubtotal, 2)
                : round($basePrice * $qty, 2);
        }

        return $storedSubtotal > 0
            ? round($storedSubtotal, 2)
            : round($basePrice * $qty, 2);
    }

    private function itemMatchesExportFilter($item, ?Request $request = null): bool
    {
        if (!$request) {
            return true;
        }

        if ((bool) ($item->is_cancelled ?? false)) {
            return false;
        }

        $material = $item->relationLoaded('material')
            ? $item->material
            : ($item->material_id ? $item->material()->first() : null);

        if ($request->filled('category_id')) {
            if (!$material || (int) $material->category_id !== (int) $request->category_id) {
                return false;
            }
        }

        if ($request->filled('machine_id') && $this->hasMaterialMachinesRelation()) {
            if (!$material) {
                return false;
            }

            $hasMachine = $material->relationLoaded('machines')
                ? $material->machines->contains(fn ($machine) => (int) $machine->id === (int) $request->machine_id)
                : $material->machines()->where('machines.id', $request->machine_id)->exists();

            if (!$hasMachine) {
                return false;
            }
        }

        return true;
    }

    private function getFilteredOrderItems(Order $order, ?Request $request = null): Collection
    {
        $items = $order->relationLoaded('items')
            ? $order->items
            : $order->items()->get();

        return $items
            ->filter(function ($item) use ($request) {
                return $this->itemMatchesExportFilter($item, $request);
            })
            ->values();
    }

    private function decorateOrderForExport(Order $order, ?Request $request = null): Order
    {
        $items = $this->getFilteredOrderItems($order, $request);

        $items->each(function ($item) {
            $item->setAttribute('size_unit', $this->normalizeUnit($item->size_unit ?? 'lembar'));
            $item->setAttribute('size_value', $this->resolveItemSizeValue($item));
            $item->setAttribute('subtotal', $this->calculateItemSubtotal($item));
        });

        $order->setRelation('items', $items);

        $reportTotal = round((float) $items->sum('subtotal'), 2);
        $reportQty = (float) $items->sum('qty');

        $order->setAttribute('total_price', $reportTotal);
        $order->setAttribute('report_total_price', $reportTotal);
        $order->setAttribute('report_total_qty', $reportQty);

        return $order;
    }

    private function getPaymentMoment($payment): ?Carbon
    {
        if (empty($payment->created_at)) {
            return null;
        }

        return $payment->created_at instanceof Carbon
            ? $payment->created_at
            : Carbon::parse($payment->created_at);
    }

    private function isPaymentInPeriod($payment, Carbon $start, Carbon $end): bool
    {
        $paidAt = $this->getPaymentMoment($payment);

        return (bool) ($paidAt && $paidAt->between($start, $end));
    }

    private function getFirstNumericValue($source, array $keys): ?float
    {
        foreach ($keys as $key) {
            $value = data_get($source, $key);

            if ($value === null || $value === '') {
                continue;
            }

            if (is_numeric($value)) {
                return (float) $value;
            }
        }

        return null;
    }

    /**
     * Menentukan nilai pembayaran yang aman untuk laporan:
     * - paid_amount      = uang yang diterima kasir
     * - change_amount    = uang kembalian ke customer
     * - net_amount       = pendapatan bersih yang diakui ke laporan
     *
     * Fallback aman:
     * - bila field kembalian tidak ada, net akan dibatasi maksimum sebesar sisa tagihan order
     * - selisih lebihnya dianggap kembalian agar total revenue tidak melebihi grand total order
     */
    private function resolvePaymentBreakdown($payment, float $orderTotal, float $runningNetBefore): array
    {
        $explicitPaid = $this->getFirstNumericValue($payment, [
            'paid_amount',
            'amount_paid',
            'received_amount',
            'cash_received',
            'customer_paid_amount',
            'nominal_dibayar',
            'total_paid',
            'pay_amount',
        ]);

        $legacyAmount = $this->getFirstNumericValue($payment, ['amount']);

        $explicitChange = $this->getFirstNumericValue($payment, [
            'change_amount',
            'cash_change',
            'returned_amount',
            'return_amount',
            'change',
            'kembalian',
            'money_change',
        ]);

        $explicitNet = $this->getFirstNumericValue($payment, [
            'net_amount',
            'amount_net',
            'net_total',
            'net_revenue',
            'revenue_amount',
            'effective_amount',
        ]);

        $paidAmount = max(0, (float) ($explicitPaid ?? $legacyAmount ?? 0));
        $changeAmount = max(0, (float) ($explicitChange ?? 0));
        $netAmount = $explicitNet !== null
            ? max(0, (float) $explicitNet)
            : max(0, $paidAmount - $changeAmount);

        if ($paidAmount <= 0 && ($netAmount > 0 || $changeAmount > 0)) {
            $paidAmount = $netAmount + $changeAmount;
        }

        $remainingDue = max(0, round($orderTotal - $runningNetBefore, 2));

        if ($remainingDue > 0) {
            $netAmount = min($netAmount, $remainingDue);

            if ($paidAmount > 0) {
                $inferredChange = max(0, round($paidAmount - $netAmount, 2));
                $changeAmount = max($changeAmount, $inferredChange);
            }
        }

        if ($paidAmount < ($netAmount + $changeAmount)) {
            $paidAmount = $netAmount + $changeAmount;
        }

        return [
            'paid_amount' => round($paidAmount, 2),
            'change_amount' => round($changeAmount, 2),
            'net_amount' => round($netAmount, 2),
        ];
    }

    private function resolveOrderCustomerName(Order $order): string
    {
        $name = $order->customer_name
            ?? data_get($order, 'customer.name')
            ?? '-';

        return trim((string) $name) !== '' ? (string) $name : '-';
    }

    /**
     * Semua pembayaran yang masuk dalam periode.
     * Nilai revenue yang dibaca adalah pendapatan bersih (dibayar - kembalian),
     * dan tidak pernah melebihi sisa tagihan order.
     */
    private function buildRevenueByPaymentRows(Collection $orders, Carbon $start, Carbon $end): Collection
    {
        return $orders
            ->flatMap(function ($order) use ($start, $end) {
                $payments = collect($order->payments ?? [])
                    ->filter(fn ($payment) => $this->getPaymentMoment($payment) !== null)
                    ->sortBy('created_at')
                    ->values();

                if ($payments->isEmpty()) {
                    return collect();
                }

                $runningNet = 0.0;
                $orderTotal = (float) ($order->report_total_price ?? $order->total_price ?? 0);

                return $payments
                    ->map(function ($payment) use ($order, $orderTotal, &$runningNet) {
                        $breakdown = $this->resolvePaymentBreakdown($payment, $orderTotal, $runningNet);
                        $runningNetBefore = $runningNet;
                        $runningNet += (float) $breakdown['net_amount'];

                        $statusLabel = 'Lunas';
                        $statusBadge = 'success';

                        if ($runningNet < $orderTotal) {
                            $statusLabel = 'Cicilan';
                            $statusBadge = 'warning';
                        } elseif ($runningNetBefore > 0) {
                            $statusLabel = 'Pelunasan';
                            $statusBadge = 'brand';
                        }

                        return (object) [
                            'order_id' => $order->id,
                            'payment_id' => $payment->id,
                            'payment_date' => $payment->created_at,
                            'invoice_number' => $order->invoice_number ?? '-',
                            'customer_name' => $this->resolveOrderCustomerName($order),
                            'payment_method' => $payment->payment_method,
                            'payment_method_label' => method_exists($payment, 'getPaymentMethodLabel')
                                ? $payment->getPaymentMethodLabel()
                                : ucfirst((string) $payment->payment_method),
                            'payment_account_label' => $payment->paymentAccount
                                ? (method_exists($payment->paymentAccount, 'getShortLabel')
                                    ? $payment->paymentAccount->getShortLabel()
                                    : ($payment->paymentAccount->name ?? '-'))
                                : '-',
                            'cashier_name' => $payment->cashier->name ?? ($order->cashier->name ?? '-'),
                            'items_label' => collect($order->items)->pluck('product_name')->filter()->unique()->implode(', '),
                            'qty' => (float) (collect($order->items)->sum('qty') ?? 0),
                            'paid_amount' => (float) $breakdown['paid_amount'],
                            'change_amount' => (float) $breakdown['change_amount'],
                            'net_amount' => (float) $breakdown['net_amount'],
                            // dipertahankan untuk kompatibilitas blade / kode lama
                            'amount' => (float) $breakdown['net_amount'],
                            'status_label' => $statusLabel,
                            'status_badge' => $statusBadge,
                            'notes' => $payment->notes,
                        ];
                    })
                    ->filter(function ($row) use ($start, $end) {
                        return $row->payment_date && Carbon::parse($row->payment_date)->between($start, $end);
                    })
                    ->values();
            })
            ->sortByDesc(function ($row) {
                return $row->payment_date ? Carbon::parse($row->payment_date)->timestamp : 0;
            })
            ->values();
    }

    /**
     * Revenue produk dihitung proporsional berdasarkan pendapatan bersih
     * yang benar-benar masuk pada periode tersebut.
     */
    private function buildRevenueByProductFromPayments(Collection $orders, Carbon $start, Carbon $end): Collection
    {
        return $orders
            ->flatMap(function ($order) use ($start, $end) {
                $items = collect($order->items ?? []);
                $orderTotal = (float) ($order->report_total_price ?? $order->total_price ?? 0);

                if ($orderTotal <= 0 || $items->isEmpty()) {
                    return collect();
                }

                $payments = collect($order->payments ?? [])
                    ->filter(fn ($payment) => $this->getPaymentMoment($payment) !== null)
                    ->sortBy('created_at')
                    ->values();

                if ($payments->isEmpty()) {
                    return collect();
                }

                $runningNet = 0.0;
                $netInPeriod = 0.0;

                foreach ($payments as $payment) {
                    $breakdown = $this->resolvePaymentBreakdown($payment, $orderTotal, $runningNet);
                    $runningNet += (float) $breakdown['net_amount'];

                    if ($this->isPaymentInPeriod($payment, $start, $end)) {
                        $netInPeriod += (float) $breakdown['net_amount'];
                    }
                }

                if ($netInPeriod <= 0) {
                    return collect();
                }

                $ratio = min(1, $netInPeriod / $orderTotal);

                return $items->map(function ($item) use ($order, $ratio) {
                    $itemSubtotal = (float) ($item->subtotal ?? 0);

                    return [
                        'order_id' => $order->id,
                        'product_id' => $item->product_id,
                        'name' => $item->product_name ?: '-',
                        'qty' => (float) ($item->qty ?? 0),
                        'total_revenue' => round($itemSubtotal * $ratio, 2),
                    ];
                });
            })
            ->groupBy(function ($row) {
                return ($row['product_id'] ?? 'null') . '|' . ($row['name'] ?? '-');
            })
            ->map(function ($rows) {
                $first = $rows->first();

                return (object) [
                    'id' => $first['product_id'],
                    'name' => $first['name'],
                    'total_orders' => $rows->pluck('order_id')->unique()->count(),
                    'total_qty' => (float) $rows->sum('qty'),
                    'total_revenue' => (float) $rows->sum('total_revenue'),
                ];
            })
            ->sortByDesc('total_revenue')
            ->values();
    }
}
