<?php

namespace App\Filament\Pages;

use App\Filament\Pages\Concerns\HasSimpleReportExports;
use App\Models\CashMovement;
use App\Models\Cashbox;
use App\Models\Customer;
use App\Models\CustomerDiscount;
use App\Models\CustomerPayment;
use App\Models\Purchase;
use App\Models\PurchaseReturn;
use App\Models\Sale;
use App\Models\SaleReturn;
use App\Models\Supplier;
use App\Models\SupplierDiscount;
use App\Models\SupplierPayment;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
use Carbon\Carbon;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Pages\Page;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Mpdf\Mpdf;

class DailyJournalReport extends Page implements HasForms
{
    use InteractsWithForms;
    use HasPageShield;
    use HasSimpleReportExports;

    protected string $view = 'filament.pages.daily-journal-report';

    public ?array $data = [];

    public ?array $report_data = null;

    protected static string|\BackedEnum|null $navigationIcon = Heroicon::OutlinedClipboardDocumentList;

    protected static ?int $navigationSort = 6;

    public static function getNavigationLabel(): string
    {
        return __('filament/admin/reports.daily_journal_report');
    }

    public function getTitle(): string
    {
        return __('filament/admin/reports.daily_journal_report');
    }

    public static function getNavigationGroup(): ?string
    {
        return __('filament.navigation_groups.cashboxes');
    }

    public static function shouldRegisterNavigation(): bool
    {
        return true;
    }

    public function mount(): void
    {
        $this->form->fill([
            'from_date' => now()->startOfDay(),
            'to_date' => now()->endOfDay(),
            'operation_types' => [],
            'customer_id' => null,
            'supplier_id' => null,
            'cashbox_id' => null,
        ]);
    }

    public function form(Schema $schema): Schema
    {
        return $schema
            ->statePath('data')
            ->components([
                Section::make(__('filament/admin/reports.filters'))
                    ->columns(3)
                    ->schema([
                        DatePicker::make('from_date')
                            ->label(__('filament/admin/reports.from_date'))
                            ->native(false)
                            ->required(),

                        DatePicker::make('to_date')
                            ->label(__('filament/admin/reports.to_date'))
                            ->native(false)
                            ->required(),

                        Select::make('operation_types')
                            ->label(__('filament/admin/reports.operation_types'))
                            ->placeholder(__('filament/admin/reports.all_operation_types'))
                            ->multiple()
                            ->searchable()
                            ->options($this->getOperationOptions()),

                        Select::make('customer_id')
                            ->label(__('filament/admin/reports.customer'))
                            ->placeholder(__('filament/admin/reports.any'))
                            ->searchable()
                            ->options(
                                Customer::query()
                                    ->orderBy('name')
                                    ->get()
                                    ->mapWithKeys(fn (Customer $customer) => [$customer->id => $customer->name])
                                    ->toArray()
                            ),

                        Select::make('supplier_id')
                            ->label(__('filament/admin/reports.supplier'))
                            ->placeholder(__('filament/admin/reports.any'))
                            ->searchable()
                            ->options(
                                Supplier::query()
                                    ->orderBy('name')
                                    ->get()
                                    ->mapWithKeys(fn (Supplier $supplier) => [$supplier->id => $supplier->name])
                                    ->toArray()
                            ),

                        Select::make('cashbox_id')
                            ->label(__('filament/admin/reports.cashbox'))
                            ->placeholder(__('filament/admin/reports.any'))
                            ->searchable()
                            ->options(
                                Cashbox::query()
                                    ->orderBy('name')
                                    ->get()
                                    ->mapWithKeys(fn (Cashbox $cashbox) => [$cashbox->id => ($cashbox->name_ar ?: $cashbox->name)])
                                    ->toArray()
                            ),
                    ]),
            ]);
    }

    public function generateReport(): void
    {
        $formData = $this->form->getState();
        $fromDate = Carbon::parse($formData['from_date'] ?? now())->startOfDay();
        $toDate = Carbon::parse($formData['to_date'] ?? now())->endOfDay();

        if ($toDate->lt($fromDate)) {
            \Filament\Notifications\Notification::make()
                ->title('تاريخ النهاية يجب أن يكون بعد أو يساوي تاريخ البداية')
                ->warning()
                ->send();
            $this->report_data = [];
            return;
        }

        $selectedTypes = collect($formData['operation_types'] ?? [])->filter()->values();
        $customerId = !empty($formData['customer_id']) ? (int) $formData['customer_id'] : null;
        $supplierId = !empty($formData['supplier_id']) ? (int) $formData['supplier_id'] : null;
        $cashboxId = !empty($formData['cashbox_id']) ? (int) $formData['cashbox_id'] : null;

        $timeline = collect()
            ->merge($this->getSalesEntries($fromDate, $toDate, $selectedTypes, $customerId, $cashboxId))
            ->merge($this->getPurchaseEntries($fromDate, $toDate, $selectedTypes, $supplierId, $cashboxId))
            ->merge($this->getSaleReturnEntries($fromDate, $toDate, $selectedTypes, $customerId, $cashboxId))
            ->merge($this->getPurchaseReturnEntries($fromDate, $toDate, $selectedTypes, $supplierId, $cashboxId))
            ->merge($this->getCustomerPaymentEntries($fromDate, $toDate, $selectedTypes, $customerId, $supplierId, $cashboxId))
            ->merge($this->getSupplierPaymentEntries($fromDate, $toDate, $selectedTypes, $customerId, $supplierId, $cashboxId))
            ->merge($this->getCustomerDiscountEntries($fromDate, $toDate, $selectedTypes, $customerId))
            ->merge($this->getSupplierDiscountEntries($fromDate, $toDate, $selectedTypes, $supplierId))
            ->merge($this->getDirectCashEntries($fromDate, $toDate, $selectedTypes, $cashboxId))
            ->sortBy([
                ['sort_date', 'asc'],
                ['sequence', 'asc'],
            ])
            ->values();

        $runningCash = 0.0;

        $timeline = $timeline->map(function (array $entry, int $index) use (&$runningCash) {
            $runningCash += (float) $entry['cash_impact'];
            $entry['index'] = $index + 1;
            $entry['running_cash'] = $runningCash;

            return $entry;
        });

        $totalAmount = (float) $timeline->sum('amount');
        $cashIn = (float) $timeline->where('cash_impact', '>', 0)->sum('cash_impact');
        $cashOut = (float) abs($timeline->where('cash_impact', '<', 0)->sum('cash_impact'));
        $netCash = $cashIn - $cashOut;

        $summaryByType = $timeline
            ->groupBy('type')
            ->map(function (Collection $group) use ($totalAmount) {
                $typeAmount = (float) $group->sum('amount');
                $typeCashIn = (float) $group->where('cash_impact', '>', 0)->sum('cash_impact');
                $typeCashOut = (float) abs($group->where('cash_impact', '<', 0)->sum('cash_impact'));

                return [
                    'count' => $group->count(),
                    'total_amount' => $typeAmount,
                    'cash_in' => $typeCashIn,
                    'cash_out' => $typeCashOut,
                    'net_cash' => $typeCashIn - $typeCashOut,
                    'share_percent' => $totalAmount > 0 ? round(($typeAmount / $totalAmount) * 100, 2) : 0,
                ];
            })
            ->toArray();

        $typeLabels = $this->getOperationOptions();

        $selectedFilters = [
            'customer' => $customerId ? Customer::query()->whereKey($customerId)->value('name') : null,
            'supplier' => $supplierId ? Supplier::query()->whereKey($supplierId)->value('name') : null,
            'cashbox' => $cashboxId
                ? (Cashbox::query()->whereKey($cashboxId)->value('name_ar') ?: Cashbox::query()->whereKey($cashboxId)->value('name'))
                : null,
        ];

        $this->report_data = [
            'from_date' => $fromDate->format('Y-m-d'),
            'to_date' => $toDate->format('Y-m-d'),
            'selected_types' => $selectedTypes->toArray(),
            'selected_filters' => $selectedFilters,
            'timeline' => $timeline,
            'type_labels' => $typeLabels,
            'summary' => [
                'total_operations' => $timeline->count(),
                'total_amount' => $totalAmount,
                'cash_in' => $cashIn,
                'cash_out' => $cashOut,
                'net_cash' => $netCash,
                'average_operation' => $timeline->count() > 0 ? $totalAmount / $timeline->count() : 0,
            ],
            'summary_by_type' => $summaryByType,
        ];
    }

    protected function getExportHeadings(): array
    {
        return [
            '#',
            __('filament/admin/reports.date'),
            __('filament/admin/reports.operation_type'),
            __('filament/admin/reports.reference_number'),
            __('filament/admin/reports.party'),
            __('filament/admin/reports.description'),
            __('filament/admin/reports.amount'),
            __('filament/admin/reports.cash_inflow'),
            __('filament/admin/reports.cash_outflow'),
            __('filament/admin/reports.net_cash_flow'),
        ];
    }

    protected function getExportRows(): array
    {
        if (empty($this->report_data['timeline'])) {
            return [];
        }

        $labels = $this->getOperationOptions();

        return collect($this->report_data['timeline'])->map(function (array $entry) use ($labels) {
            return [
                $entry['index'],
                $entry['date'],
                $labels[$entry['type']] ?? $entry['type'],
                $entry['reference_number'] ?: '-',
                $entry['party'] ?: __('filament/admin/reports.unknown_party'),
                $entry['description'],
                number_format((float) $entry['amount'], 2),
                $entry['cash_impact'] > 0 ? number_format((float) $entry['cash_impact'], 2) : '0.00',
                $entry['cash_impact'] < 0 ? number_format(abs((float) $entry['cash_impact']), 2) : '0.00',
                number_format((float) $entry['running_cash'], 2),
            ];
        })->toArray();
    }

    protected function getExportFileName(): string
    {
        return 'daily-journal-report';
    }

    public function exportPdf()
    {
        if (empty($this->report_data)) {
            $this->generateReport();
        }

        $mpdf = new Mpdf([
            'mode' => 'utf-8',
            'format' => 'A4',
            'orientation' => 'L',
            'default_font' => 'dejavusans',
            'autoScriptToLang' => true,
            'autoLangToFont' => true,
            'margin_top' => 10,
            'margin_bottom' => 10,
            'margin_left' => 8,
            'margin_right' => 8,
        ]);

        $mpdf->SetDirectionality('rtl');

        $html = view('reports.pdf.daily-journal-report', [
            'data' => $this->report_data,
        ])->render();

        $mpdf->WriteHTML($html);

        return response()->streamDownload(function () use ($mpdf) {
            echo $mpdf->Output('', 'S');
        }, 'daily-journal-report-' . now()->format('Y-m-d') . '.pdf');
    }

    private function getOperationOptions(): array
    {
        return [
            'sale' => __('filament/admin/reports.sale'),
            'purchase' => __('filament/admin/reports.purchase'),
            'sale_return' => __('filament/admin/reports.sale_return'),
            'purchase_return' => __('filament/admin/reports.purchase_return'),
            'customer_payment' => __('filament/admin/reports.customer_payment'),
            'supplier_payment' => __('filament/admin/reports.supplier_payment'),
            'customer_discount' => __('filament/admin/reports.customer_discount'),
            'supplier_discount' => __('filament/admin/reports.supplier_discount'),
            'cash_in' => __('filament/admin/reports.cash_in'),
            'cash_out' => __('filament/admin/reports.cash_out'),
        ];
    }

    private function shouldInclude(string $type, Collection $selectedTypes): bool
    {
        return $selectedTypes->isEmpty() || $selectedTypes->contains($type);
    }

    private function getSalesEntries(
        Carbon $fromDate,
        Carbon $toDate,
        Collection $selectedTypes,
        ?int $customerId,
        ?int $cashboxId,
    ): Collection
    {
        if (!$this->shouldInclude('sale', $selectedTypes)) {
            return collect();
        }

        $sales = Sale::query()
            ->with('customer')
            ->where('status', 'confirmed')
            ->whereDate('sale_date', '>=', $fromDate)
            ->whereDate('sale_date', '<=', $toDate);

        if ($customerId) {
            $sales->where('customer_id', $customerId);
        }

        if ($cashboxId) {
            $sales->where('cashbox_id', $cashboxId);
        }

        $sales = $sales->get();

        return $sales->map(fn (Sale $sale) => $this->makeEntry(
            type: 'sale',
            businessDate: Carbon::parse($sale->sale_date),
            createdAt: $sale->created_at,
            referenceNumber: $sale->sale_number,
            party: $sale->customer?->name,
            description: __('filament/admin/reports.sale_invoice') . ' - ' . ($sale->customer?->name ?? __('filament/admin/reports.unknown_party')),
            amount: (float) $sale->total_amount,
            cashImpact: (float) $sale->paid_amount,
            sequence: 10,
        ));
    }

    private function getPurchaseEntries(
        Carbon $fromDate,
        Carbon $toDate,
        Collection $selectedTypes,
        ?int $supplierId,
        ?int $cashboxId,
    ): Collection
    {
        if (!$this->shouldInclude('purchase', $selectedTypes)) {
            return collect();
        }

        $purchases = Purchase::query()
            ->with('supplier')
            ->where('status', 'confirmed')
            ->whereDate('purchase_date', '>=', $fromDate)
            ->whereDate('purchase_date', '<=', $toDate);

        if ($supplierId) {
            $purchases->where('supplier_id', $supplierId);
        }

        if ($cashboxId) {
            $purchases->where('cashbox_id', $cashboxId);
        }

        $purchases = $purchases->get();

        return $purchases->map(fn (Purchase $purchase) => $this->makeEntry(
            type: 'purchase',
            businessDate: Carbon::parse($purchase->purchase_date),
            createdAt: $purchase->created_at,
            referenceNumber: $purchase->purchase_number,
            party: $purchase->supplier?->name,
            description: __('filament/admin/reports.purchase_invoice') . ' - ' . ($purchase->supplier?->name ?? __('filament/admin/reports.unknown_party')),
            amount: (float) $purchase->total_amount,
            cashImpact: -(float) $purchase->paid_amount,
            sequence: 20,
        ));
    }

    private function getSaleReturnEntries(
        Carbon $fromDate,
        Carbon $toDate,
        Collection $selectedTypes,
        ?int $customerId,
        ?int $cashboxId,
    ): Collection
    {
        if (!$this->shouldInclude('sale_return', $selectedTypes)) {
            return collect();
        }

        $returns = SaleReturn::query()
            ->with('customer')
            ->where('status', 'confirmed')
            ->whereBetween('return_date', [$fromDate, $toDate]);

        if ($customerId) {
            $returns->where('customer_id', $customerId);
        }

        if ($cashboxId) {
            $returns->where('cashbox_id', $cashboxId);
        }

        $returns = $returns->get();

        return $returns->map(fn (SaleReturn $return) => $this->makeEntry(
            type: 'sale_return',
            businessDate: Carbon::parse($return->return_date),
            createdAt: $return->created_at,
            referenceNumber: $return->return_number,
            party: $return->customer?->name,
            description: __('filament/admin/reports.customer_return') . ' - ' . ($return->customer?->name ?? __('filament/admin/reports.unknown_party')),
            amount: (float) $return->total_amount,
            cashImpact: -(float) $return->paid_amount,
            sequence: 30,
        ));
    }

    private function getPurchaseReturnEntries(
        Carbon $fromDate,
        Carbon $toDate,
        Collection $selectedTypes,
        ?int $supplierId,
        ?int $cashboxId,
    ): Collection
    {
        if (!$this->shouldInclude('purchase_return', $selectedTypes)) {
            return collect();
        }

        $returns = PurchaseReturn::query()
            ->with('supplier')
            ->where('status', 'confirmed')
            ->whereDate('return_date', '>=', $fromDate)
            ->whereDate('return_date', '<=', $toDate);

        if ($supplierId) {
            $returns->where('supplier_id', $supplierId);
        }

        if ($cashboxId) {
            $returns->where('cashbox_id', $cashboxId);
        }

        $returns = $returns->get();

        return $returns->map(fn (PurchaseReturn $return) => $this->makeEntry(
            type: 'purchase_return',
            businessDate: Carbon::parse($return->return_date),
            createdAt: $return->created_at,
            referenceNumber: $return->return_number,
            party: $return->supplier?->name,
            description: __('filament/admin/reports.supplier_return') . ' - ' . ($return->supplier?->name ?? __('filament/admin/reports.unknown_party')),
            amount: (float) $return->total_amount,
            cashImpact: (float) $return->paid_amount,
            sequence: 40,
        ));
    }

    private function getCustomerPaymentEntries(
        Carbon $fromDate,
        Carbon $toDate,
        Collection $selectedTypes,
        ?int $customerId,
        ?int $supplierId,
        ?int $cashboxId,
    ): Collection
    {
        if (!$this->shouldInclude('customer_payment', $selectedTypes)) {
            return collect();
        }

        $payments = CustomerPayment::query()
            ->with(['customer', 'supplier'])
            ->where('status', 'confirmed')
            ->whereDate('payment_date', '>=', $fromDate)
            ->whereDate('payment_date', '<=', $toDate);

        if ($customerId) {
            $payments->where('customer_id', $customerId);
        }

        if ($supplierId) {
            $payments->where('supplier_id', $supplierId);
        }

        if ($cashboxId) {
            $payments->where('cashbox_id', $cashboxId);
        }

        $payments = $payments->get();

        return $payments->map(function (CustomerPayment $payment) {
            $party = $payment->customer?->name ?? $payment->supplier?->name;

            return $this->makeEntry(
                type: 'customer_payment',
                businessDate: Carbon::parse($payment->payment_date),
                createdAt: $payment->created_at,
                referenceNumber: $payment->receipt_number,
                party: $party,
                description: __('filament/admin/reports.customer_payment_received') . ' - ' . ($party ?? __('filament/admin/reports.unknown_party')),
                amount: (float) $payment->amount,
                cashImpact: (float) $payment->amount,
                sequence: 50,
            );
        });
    }

    private function getSupplierPaymentEntries(
        Carbon $fromDate,
        Carbon $toDate,
        Collection $selectedTypes,
        ?int $customerId,
        ?int $supplierId,
        ?int $cashboxId,
    ): Collection
    {
        if (!$this->shouldInclude('supplier_payment', $selectedTypes)) {
            return collect();
        }

        $payments = SupplierPayment::query()
            ->with(['supplier', 'customer'])
            ->where('status', 'confirmed')
            ->whereDate('payment_date', '>=', $fromDate)
            ->whereDate('payment_date', '<=', $toDate);

        if ($supplierId) {
            $payments->where('supplier_id', $supplierId);
        }

        if ($customerId) {
            $payments->where('customer_id', $customerId);
        }

        if ($cashboxId) {
            $payments->where('cashbox_id', $cashboxId);
        }

        $payments = $payments->get();

        return $payments->map(function (SupplierPayment $payment) {
            $party = $payment->supplier?->name ?? $payment->customer?->name;

            return $this->makeEntry(
                type: 'supplier_payment',
                businessDate: Carbon::parse($payment->payment_date),
                createdAt: $payment->created_at,
                referenceNumber: $payment->disbursement_number,
                party: $party,
                description: __('filament/admin/reports.supplier_payment_made') . ' - ' . ($party ?? __('filament/admin/reports.unknown_party')),
                amount: (float) $payment->amount,
                cashImpact: -(float) $payment->amount,
                sequence: 60,
            );
        });
    }

    private function getDirectCashEntries(
        Carbon $fromDate,
        Carbon $toDate,
        Collection $selectedTypes,
        ?int $cashboxId,
    ): Collection
    {
        $inAllowed = $this->shouldInclude('cash_in', $selectedTypes);
        $outAllowed = $this->shouldInclude('cash_out', $selectedTypes);

        if (!$inAllowed && !$outAllowed) {
            return collect();
        }

        $excludedReferenceTypes = [
            'Sale',
            'Purchase',
            'SaleReturn',
            'PurchaseReturn',
            'CustomerPayment',
            'SupplierPayment',
            'CustomerDiscount',
            'SupplierDiscount',
            'customer_discount',
            'supplier_discount',
        ];

        $query = CashMovement::query()
            ->with('cashbox')
            ->whereDate('date', '>=', $fromDate)
            ->whereDate('date', '<=', $toDate)
            ->where(function (Builder $builder) use ($excludedReferenceTypes) {
                $builder->whereNull('reference_type')
                    ->orWhereNotIn('reference_type', $excludedReferenceTypes);
            });

        if ($cashboxId) {
            $query->where('cashbox_id', $cashboxId);
        }

        if ($inAllowed xor $outAllowed) {
            $query->where('movement_type', $inAllowed ? 'in' : 'out');
        }

        return $query->get()->map(function (CashMovement $movement) {
            $type = $movement->movement_type === 'in' ? 'cash_in' : 'cash_out';
            $description = $movement->description ?: ($movement->movement_type === 'in'
                ? __('filament/admin/reports.cash_deposit')
                : __('filament/admin/reports.cash_withdrawal'));

            return $this->makeEntry(
                type: $type,
                businessDate: Carbon::parse($movement->date),
                createdAt: $movement->created_at,
                referenceNumber: $movement->reference_number,
                party: $movement->cashbox?->name_ar ?? $movement->cashbox?->name,
                description: $description,
                amount: (float) $movement->amount,
                cashImpact: $movement->movement_type === 'in' ? (float) $movement->amount : -(float) $movement->amount,
                sequence: 70,
            );
        });
    }

    private function getCustomerDiscountEntries(
        Carbon $fromDate,
        Carbon $toDate,
        Collection $selectedTypes,
        ?int $customerId,
    ): Collection
    {
        if (!$this->shouldInclude('customer_discount', $selectedTypes)) {
            return collect();
        }

        $discounts = CustomerDiscount::query()
            ->with('customer')
            ->where('status', 'confirmed')
            ->whereDate('discount_date', '>=', $fromDate)
            ->whereDate('discount_date', '<=', $toDate);

        if ($customerId) {
            $discounts->where('customer_id', $customerId);
        }

        $discounts = $discounts->get();

        return $discounts->map(fn (CustomerDiscount $discount) => $this->makeEntry(
            type: 'customer_discount',
            businessDate: Carbon::parse($discount->discount_date),
            createdAt: $discount->created_at,
            referenceNumber: $discount->discount_number,
            party: $discount->customer?->name,
            description: __('filament/admin/reports.customer_discount_granted') . ' - ' . ($discount->customer?->name ?? __('filament/admin/reports.unknown_party')),
            amount: (float) $discount->amount,
            cashImpact: 0,
            sequence: 70,
        ));
    }

    private function getSupplierDiscountEntries(
        Carbon $fromDate,
        Carbon $toDate,
        Collection $selectedTypes,
        ?int $supplierId,
    ): Collection
    {
        if (!$this->shouldInclude('supplier_discount', $selectedTypes)) {
            return collect();
        }

        $discounts = SupplierDiscount::query()
            ->with('supplier')
            ->where('status', 'confirmed')
            ->whereDate('discount_date', '>=', $fromDate)
            ->whereDate('discount_date', '<=', $toDate);

        if ($supplierId) {
            $discounts->where('supplier_id', $supplierId);
        }

        $discounts = $discounts->get();

        return $discounts->map(fn (SupplierDiscount $discount) => $this->makeEntry(
            type: 'supplier_discount',
            businessDate: Carbon::parse($discount->discount_date),
            createdAt: $discount->created_at,
            referenceNumber: $discount->discount_number,
            party: $discount->supplier?->name,
            description: __('filament/admin/reports.supplier_discount_received') . ' - ' . ($discount->supplier?->name ?? __('filament/admin/reports.unknown_party')),
            amount: (float) $discount->amount,
            cashImpact: 0,
            sequence: 80,
        ));
    }

    private function makeEntry(
        string $type,
        Carbon $businessDate,
        ?Carbon $createdAt,
        ?string $referenceNumber,
        ?string $party,
        string $description,
        float $amount,
        float $cashImpact,
        int $sequence,
    ): array {
        $sortDate = $businessDate->copy();

        if ($createdAt) {
            $sortDate->setTime(
                (int) $createdAt->format('H'),
                (int) $createdAt->format('i'),
                (int) $createdAt->format('s'),
            );
        }

        return [
            'type' => $type,
            'date' => $sortDate->format('Y-m-d H:i:s'),
            'sort_date' => $sortDate,
            'reference_number' => $referenceNumber,
            'party' => $party,
            'description' => $description,
            'amount' => $amount,
            'cash_impact' => $cashImpact,
            'sequence' => $sequence,
        ];
    }
}
