<?php

namespace App\Http\Controllers;

use App\Models\CashModal;
use App\Models\CashMutation;
use App\Models\Deposit;
use App\Models\Expense;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Payment;
use App\Models\PurchasePayment;
use App\Models\Salary;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class FinanceController extends Controller
{
    private const AUTO_DEPOSIT_TAG = '[AUTO-DEPOSIT]';

    public function __construct()
    {
        $this->middleware(\App\Http\Middleware\PermissionMiddleware::class . ':view finance')
            ->only(['index', 'cash']);
    }

    public function index(Request $request)
    {
        $startDate = $request->input('start_date', now()->startOfMonth()->toDateString());
        $endDate = $request->input('end_date', now()->toDateString());
        $perPage = $request->input('per_page', 15);

        $ordersQuery = Order::with(['designer', 'cashier', 'items'])
            ->paid()
            ->whereBetween('paid_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']);

        $allOrderIds = (clone $ordersQuery)->pluck('id');
        $totalQty = OrderItem::whereIn('order_id', $allOrderIds)->sum('qty');

        $summary = [
            'total_orders' => (clone $ordersQuery)->count(),
            'total_revenue' => (clone $ordersQuery)->sum('total_price'),
            'total_qty' => $totalQty,
        ];

        $orders = $ordersQuery->latest('paid_at')
            ->paginate($perPage)
            ->withQueryString();

        $dailyData = Order::paid()
            ->whereBetween('paid_at', [$startDate . ' 00:00:00', $endDate . ' 23:59:59'])
            ->select(
                DB::raw('DATE(paid_at) as date'),
                DB::raw('COUNT(*) as total_orders'),
                DB::raw('SUM(total_price) as total_revenue')
            )
            ->groupBy('date')
            ->orderBy('date')
            ->get();

        return view('pages.finance.index', [
            'title' => 'Laporan Keuangan',
            'orders' => $orders,
            'summary' => $summary,
            'dailyData' => $dailyData,
            'startDate' => $startDate,
            'endDate' => $endDate,
        ]);
    }

    public function cash(Request $request)
    {
        $filterDate = $request->input('date', now()->toDateString());
        $focusDate = Carbon::parse($filterDate)->startOfDay();

        $dayStart = $focusDate->copy()->startOfDay();
        $dayEnd = $focusDate->copy()->endOfDay();

        $monthStart = $focusDate->copy()->startOfMonth()->startOfDay();
        $monthEnd = $focusDate->copy()->endOfMonth()->endOfDay();

        $untilEnd = $focusDate->copy()->endOfDay();

        $context = [
            'paymentDateField' => $this->pickDateField('payments', ['payment_date', 'paid_at', 'created_at']),
            'purchasePayDateField' => Schema::hasTable('purchase_payments')
                ? $this->pickDateField('purchase_payments', ['payment_date', 'created_at'])
                : null,
            'expenseDateField' => Schema::hasTable('expenses')
                ? $this->pickDateField('expenses', ['expense_date', 'created_at'])
                : null,
            'depositDateField' => Schema::hasTable('deposits')
                ? $this->pickDateField('deposits', ['deposit_date', 'created_at'])
                : null,
            'modalDateField' => Schema::hasTable('cash_modals')
                ? $this->pickDateField('cash_modals', ['modal_date', 'created_at'])
                : null,
            'mutationDateField' => Schema::hasTable('cash_mutations')
                ? $this->pickDateField('cash_mutations', ['mutation_date', 'created_at'])
                : null,
            'paymentsHasAccount' => Schema::hasColumn('payments', 'payment_account_id'),
            'paymentsHasMethod' => Schema::hasColumn('payments', 'payment_method'),
            'purchasePaysHasAccount' => Schema::hasTable('purchase_payments') && Schema::hasColumn('purchase_payments', 'payment_account_id'),
            'purchasePaysHasMethod' => Schema::hasTable('purchase_payments') && Schema::hasColumn('purchase_payments', 'payment_method'),
        ];

        $kasKecil = $this->buildCashBalance('petty', null, $untilEnd, $context);
        $kasKecilBulanan = $this->buildCashBalance('petty', $monthStart, $monthEnd, $context);
        $kasKecilHarian = $this->buildCashBalance('petty', $dayStart, $dayEnd, $context);

        $kasTunai = $this->buildCashBalance('cash', null, $untilEnd, $context);
        $kasTunaiBulanan = $this->buildCashBalance('cash', $monthStart, $monthEnd, $context);
        $kasTunaiHarian = $this->buildCashBalance('cash', $dayStart, $dayEnd, $context);

        $kasBank = $this->buildCashBalance('bank', null, $untilEnd, $context);
        $kasBankBulanan = $this->buildCashBalance('bank', $monthStart, $monthEnd, $context);
        $kasBankHarian = $this->buildCashBalance('bank', $dayStart, $dayEnd, $context);

        $depositMonth = $this->sumDeposits($monthStart, $monthEnd, $context['depositDateField']);

        $summary = [
            'kas_kecil' => $kasKecil,
            'kas_kecil_bulanan' => $kasKecilBulanan,
            'kas_kecil_harian' => $kasKecilHarian,

            'kas_tunai' => $kasTunai,
            'kas_tunai_bulanan' => $kasTunaiBulanan,
            'kas_tunai_harian' => $kasTunaiHarian,

            'kas_bank' => $kasBank,
            'kas_bank_bulanan' => $kasBankBulanan,
            'kas_bank_harian' => $kasBankHarian,

            'total_setoran' => $depositMonth,
        ];

        return view('pages.finance.cash', [
            'title' => 'Laporan Kas',
            'summary' => $summary,
            'filterDate' => $filterDate,
        ]);
    }

    private function buildCashBalance(string $account, ?Carbon $start, ?Carbon $end, array $context): float
    {
        if ($account === 'petty') {
            return
                $this->sumCashModal('petty', $start, $end, $context['modalDateField']) +
                $this->sumNetMutation('petty', $start, $end, $context['mutationDateField'], true) -
                $this->sumPettyExpenses($start, $end, $context['expenseDateField']);
        }

        if ($account === 'cash') {
            return
                $this->sumPaymentsByBucket(
                    'cash',
                    $start,
                    $end,
                    $context['paymentDateField'],
                    $context['paymentsHasAccount'],
                    $context['paymentsHasMethod']
                ) +
                $this->sumCashModal('cash', $start, $end, $context['modalDateField']) +
                $this->sumNetMutation('cash', $start, $end, $context['mutationDateField'], true) -
                $this->sumDeposits($start, $end, $context['depositDateField']) -
                $this->sumPurchasePaymentsByBucket(
                    'cash',
                    $start,
                    $end,
                    $context['purchasePayDateField'],
                    $context['purchasePaysHasAccount'],
                    $context['purchasePaysHasMethod']
                ) -
                $this->sumPaidSalariesBySource('cash', $start, $end);
        }

        if ($account === 'bank') {
            return
                $this->sumPaymentsByBucket(
                    'bank',
                    $start,
                    $end,
                    $context['paymentDateField'],
                    $context['paymentsHasAccount'],
                    $context['paymentsHasMethod']
                ) +
                $this->sumDeposits($start, $end, $context['depositDateField']) +
                $this->sumCashModal('bank', $start, $end, $context['modalDateField']) +
                $this->sumNetMutation('bank', $start, $end, $context['mutationDateField'], true) -
                $this->sumPurchasePaymentsByBucket(
                    'bank',
                    $start,
                    $end,
                    $context['purchasePayDateField'],
                    $context['purchasePaysHasAccount'],
                    $context['purchasePaysHasMethod']
                ) -
                $this->sumPaidSalariesBySource('bank', $start, $end);
        }

        return 0.0;
    }

    private function sumPaymentsByBucket(
        string $bucket,
        ?Carbon $start,
        ?Carbon $end,
        string $paymentDateField,
        bool $hasAccountColumn,
        bool $hasMethodColumn
    ): float {
        $query = Payment::query()->orderBy($paymentDateField);

        $this->applyDateTimeRange($query, $paymentDateField, $start, $end);
        $this->applyActiveOrderFilterToPayments($query);

        if ($bucket === 'cash') {
            if (!$hasAccountColumn && !$hasMethodColumn) {
                return (float) $query->sum('amount');
            }

            $query->where(function ($q) use ($hasAccountColumn, $hasMethodColumn) {
                $added = false;

                if ($hasMethodColumn) {
                    $q->where('payment_method', 'cash');
                    $added = true;
                }

                if ($hasAccountColumn) {
                    if ($added) {
                        $q->orWhereNull('payment_account_id');
                    } else {
                        $q->whereNull('payment_account_id');
                    }
                }
            });

            return (float) $query->sum('amount');
        }

        if ($bucket === 'bank') {
            if (!$hasAccountColumn && !$hasMethodColumn) {
                return (float) $query->sum('amount');
            }

            $query->where(function ($q) use ($hasAccountColumn, $hasMethodColumn) {
                $added = false;

                if ($hasMethodColumn) {
                    $q->whereIn('payment_method', ['transfer', 'ewallet']);
                    $added = true;
                }

                if ($hasAccountColumn) {
                    if ($added) {
                        $q->orWhereNotNull('payment_account_id');
                    } else {
                        $q->whereNotNull('payment_account_id');
                    }
                }
            });

            return (float) $query->sum('amount');
        }

        return 0.0;
    }

    private function sumPettyExpenses(?Carbon $start, ?Carbon $end, ?string $expenseDateField): float
    {
        if (!Schema::hasTable('expenses') || !$expenseDateField) {
            return 0.0;
        }

        $query = Expense::query()
            ->where(function ($q) {
                $q->whereNull('description')
                    ->orWhere('description', 'not like', '%purchase_payment:%');
            });

        $this->applyDateTimeRange($query, $expenseDateField, $start, $end);

        return (float) $query->sum('amount');
    }

    private function sumDeposits(?Carbon $start, ?Carbon $end, ?string $depositDateField): float
    {
        if (!Schema::hasTable('deposits') || !$depositDateField) {
            return 0.0;
        }

        $query = Deposit::query();
        $this->applyDateTimeRange($query, $depositDateField, $start, $end);

        return (float) $query->sum('amount');
    }

    private function sumPurchasePaymentsByBucket(
        string $bucket,
        ?Carbon $start,
        ?Carbon $end,
        ?string $purchasePayDateField,
        bool $hasAccountColumn,
        bool $hasMethodColumn
    ): float {
        if (!Schema::hasTable('purchase_payments') || !$purchasePayDateField) {
            return 0.0;
        }

        $query = PurchasePayment::query()->orderBy($purchasePayDateField);
        $this->applyDateTimeRange($query, $purchasePayDateField, $start, $end);

        $rows = $query->get();

        return (float) $rows->filter(function ($item) use ($bucket, $hasAccountColumn, $hasMethodColumn) {
            $method = $this->resolvePurchasePaymentMethod($item, $hasAccountColumn, $hasMethodColumn);

            if ($bucket === 'cash') {
                return $method === 'cash';
            }

            if ($bucket === 'bank') {
                return $method === 'transfer';
            }

            return false;
        })->sum('amount');
    }

    private function resolvePurchasePaymentMethod($item, bool $hasAccountColumn, bool $hasMethodColumn): ?string
    {
        if ($hasMethodColumn && !empty($item->payment_method)) {
            return strtolower(trim((string) $item->payment_method));
        }

        $info = $this->extractPaymentInfoFromNotes((string) ($item->notes ?? ''));

        if (!empty($info['method'])) {
            return $info['method'];
        }

        if ($hasAccountColumn && !empty($item->payment_account_id)) {
            return 'transfer';
        }

        return null;
    }

    private function sumCashModal(string $account, ?Carbon $start, ?Carbon $end, ?string $modalDateField): float
    {
        if (!Schema::hasTable('cash_modals') || !$modalDateField) {
            return 0.0;
        }

        $query = CashModal::query()->where('account', $account);
        $this->applyDateTimeRange($query, $modalDateField, $start, $end);

        return (float) $query->sum('amount');
    }

    private function sumNetMutation(
        string $account,
        ?Carbon $start,
        ?Carbon $end,
        ?string $mutationDateField,
        bool $excludeAutoDeposit = true
    ): float {
        if (!Schema::hasTable('cash_mutations') || !$mutationDateField) {
            return 0.0;
        }

        $query = CashMutation::query();

        if ($excludeAutoDeposit) {
            $query->where(function ($q) {
                $q->whereNull('notes')
                    ->orWhere('notes', 'not like', '%' . self::AUTO_DEPOSIT_TAG . '%');
            });
        }

        $this->applyDateTimeRange($query, $mutationDateField, $start, $end);

        $in = (clone $query)->where('to_account', $account)->sum('amount');
        $out = (clone $query)->where('from_account', $account)->sum('amount');

        return (float) $in - (float) $out;
    }

    private function sumPaidSalariesBySource(string $source, ?Carbon $start, ?Carbon $end): float
    {
        if (!Schema::hasTable('salaries')) {
            return 0.0;
        }

        $dateField = Schema::hasColumn('salaries', 'paid_at')
            ? 'paid_at'
            : (Schema::hasColumn('salaries', 'updated_at') ? 'updated_at' : 'created_at');

        $query = Salary::query()->where('status', 'paid');

        if (Schema::hasColumn('salaries', 'payment_source')) {
            $query->where('payment_source', $source);
        } elseif (Schema::hasColumn('salaries', 'payment_method')) {
            $query->where('payment_method', $source === 'cash' ? 'cash' : 'transfer');
        } elseif (Schema::hasColumn('salaries', 'payment_account_id')) {
            if ($source === 'cash') {
                $query->whereNull('payment_account_id');
            } else {
                $query->whereNotNull('payment_account_id');
            }
        } else {
            return 0.0;
        }

        $this->applyDateTimeRange($query, $dateField, $start, $end);

        return (float) $query->get()->sum(function ($salary) {
            return
                (float) ($salary->base_salary ?? 0) +
                (float) ($salary->allowance ?? 0) +
                (float) ($salary->overtime ?? 0) +
                (float) ($salary->bonus ?? 0) -
                (float) ($salary->deduction ?? 0);
        });
    }

    private function pickDateField(string $table, array $candidates): string
    {
        foreach ($candidates as $column) {
            if (Schema::hasColumn($table, $column)) {
                return $column;
            }
        }

        return 'created_at';
    }

    private function applyActiveOrderFilterToPayments(Builder $query): void
    {
        if (!Schema::hasColumn('payments', 'order_id')) {
            return;
        }

        $query->where(function ($q) {
            $q->whereNull('order_id')
                ->orWhereHas('order', function ($orderQuery) {
                    $orderQuery->where('is_cancelled', false);
                });
        });
    }

    private function extractPaymentInfoFromNotes(string $notes): array
    {
        $normalized = trim($notes);

        $method = null;
        $rekening = null;

        if (preg_match('/Metode:\s*Bank Transfer/i', $normalized)) {
            $method = 'transfer';
        } elseif (preg_match('/Metode:\s*Cash/i', $normalized)) {
            $method = 'cash';
        }

        if (preg_match('/Rekening:\s*(.+?)(\s*\|\s*|$)/i', $normalized, $matches)) {
            $rekening = trim($matches[1] ?? '');
        }

        return [
            'method' => $method,
            'rekening' => $rekening,
        ];
    }

    private function applyDateTimeRange($query, string $column, ?Carbon $start, ?Carbon $end): void
    {
        if ($start) {
            $query->where($column, '>=', $start->copy()->startOfDay());
        }

        if ($end) {
            $query->where($column, '<=', $end->copy()->endOfDay());
        }
    }
}