<?php

namespace App\Services\Reports;

use App\Enums\CrmModule;
use App\Enums\ExpenseCategory;
use App\Models\Booking;
use App\Models\Contractor;
use App\Models\Expense;
use App\Models\MaterialStock;
use App\Models\Project;
use App\Models\ProjectPayment;
use App\Models\Receipt;
use App\Models\VendorPayment;
use App\Services\Dashboard\DashboardFilterBag;
use Illuminate\Support\Facades\DB;

class ProfitLossReportService
{
    /**
     * @return array{
     *     period_label: string,
     *     sections: array<int, array{title: string, lines: array<int, array{label: string, amount: float, note?: string}>, subtotal: float}>,
     *     total_revenue: float,
     *     total_direct_costs: float,
     *     gross_profit: float,
     *     total_operating_expenses: float,
     *     net_profit: float,
     *     supplementary: array<int, array{label: string, amount: float, note?: string}>
     * }
     */
    public static function construction(DashboardFilterBag $filters): array
    {
        $contractValue = self::constructionContractValue($filters);
        $revenue = [
            ['label' => 'Contract Value (Theka Amount)', 'amount' => $contractValue, 'note' => 'Total value of construction contracts'],
        ];
        $materialPurchases = self::constructionMaterialPurchases($filters);
        $directExpenseLines = self::expenseLines(
            $filters,
            [CrmModule::Construction, CrmModule::Shared],
            [
                ExpenseCategory::Labor,
                ExpenseCategory::Salary,
                ExpenseCategory::Equipment,
                ExpenseCategory::Transport,
            ],
        );
        $directExpenseTotal = self::sumLines($directExpenseLines);
        $contractorCosts = self::constructionContractorCosts($filters);

        $directLines = array_merge(
            [
                ['label' => 'Material Purchases', 'amount' => $materialPurchases],
                ['label' => 'Subcontractor Payments', 'amount' => $contractorCosts, 'note' => 'Cumulative paid amount on record'],
            ],
            $directExpenseLines,
        );
        $totalDirectCosts = $materialPurchases + $directExpenseTotal + $contractorCosts;

        $operatingLines = self::expenseLines(
            $filters,
            [CrmModule::Construction, CrmModule::Shared],
            [
                ExpenseCategory::Office,
                ExpenseCategory::Rent,
                ExpenseCategory::Utilities,
                ExpenseCategory::Marketing,
                ExpenseCategory::Maintenance,
                ExpenseCategory::Commission,
                ExpenseCategory::Legal,
                ExpenseCategory::Tax,
                ExpenseCategory::Personal,
                ExpenseCategory::Miscellaneous,
            ],
        );
        $totalOperating = self::sumLines($operatingLines);

        $totalRevenue = self::sumLines($revenue);
        $grossProfit = $totalRevenue - $totalDirectCosts;
        $netProfit = $grossProfit - $totalOperating;

        return [
            'period_label' => $filters->periodLabel(),
            'sections' => [
                [
                    'title' => 'Income (Contract Value)',
                    'lines' => $revenue,
                    'subtotal' => $totalRevenue,
                ],
                [
                    'title' => 'Direct Project Costs',
                    'lines' => $directLines,
                    'subtotal' => $totalDirectCosts,
                ],
                [
                    'title' => 'Operating Expenses',
                    'lines' => $operatingLines,
                    'subtotal' => $totalOperating,
                ],
            ],
            'total_revenue' => $totalRevenue,
            'total_direct_costs' => $totalDirectCosts,
            'gross_profit' => $grossProfit,
            'total_operating_expenses' => $totalOperating,
            'net_profit' => $netProfit,
            'supplementary' => [
                [
                    'label' => 'Client Payments Received (Cash In)',
                    'amount' => self::constructionClientPayments($filters),
                    'note' => 'Paisa jo client se mila',
                ],
                [
                    'label' => 'Pending from Clients',
                    'amount' => max(0, $contractValue - self::constructionClientPayments($filters)),
                    'note' => 'Contract value minus received',
                ],
                [
                    'label' => 'Vendor Payments (Cash Out)',
                    'amount' => self::constructionVendorPayments($filters),
                    'note' => 'Cash paid to vendors in this period',
                ],
            ],
        ];
    }

    /**
     * @return array{
     *     period_label: string,
     *     sections: array<int, array{title: string, lines: array<int, array{label: string, amount: float, note?: string}>, subtotal: float}>,
     *     total_revenue: float,
     *     total_direct_costs: float,
     *     gross_profit: float,
     *     total_operating_expenses: float,
     *     net_profit: float,
     *     supplementary: array<int, array{label: string, amount: float, note?: string}>
     * }
     */
    public static function realEstate(DashboardFilterBag $filters): array
    {
        $revenue = self::realEstateRevenue($filters);
        $bookingValue = self::realEstateBookingValue($filters);

        $operatingLines = self::expenseLines(
            $filters,
            [CrmModule::RealEstate, CrmModule::Shared],
            ExpenseCategory::cases(),
        );
        $totalOperating = self::sumLines($operatingLines);

        $totalRevenue = self::sumLines($revenue);
        $netProfit = $totalRevenue - $totalOperating;

        return [
            'period_label' => $filters->periodLabel(),
            'sections' => [
                [
                    'title' => 'Revenue (Cash Collected)',
                    'lines' => $revenue,
                    'subtotal' => $totalRevenue,
                ],
                [
                    'title' => 'Sales & Bookings',
                    'lines' => [
                        ['label' => 'Booking Value (Committed Sales)', 'amount' => $bookingValue, 'note' => 'Not cash — booking amount in period'],
                    ],
                    'subtotal' => $bookingValue,
                ],
                [
                    'title' => 'Operating Expenses',
                    'lines' => $operatingLines,
                    'subtotal' => $totalOperating,
                ],
            ],
            'total_revenue' => $totalRevenue,
            'total_direct_costs' => 0,
            'gross_profit' => $totalRevenue,
            'total_operating_expenses' => $totalOperating,
            'net_profit' => $netProfit,
            'supplementary' => [],
        ];
    }

    /**
     * @return array<int, array{label: string, amount: float, note?: string}>
     */
    private static function realEstateRevenue(DashboardFilterBag $filters): array
    {
        $query = Receipt::query()
            ->whereIn('module', [CrmModule::RealEstate, CrmModule::Shared]);
        $filters->applyDateRange($query, 'paid_at');

        if ($filters->projectId) {
            $query->where(function ($q) use ($filters): void {
                $q->whereHas('invoice', fn ($iq) => $iq->where('project_id', $filters->projectId))
                    ->orWhereHas('installment.paymentPlan.booking.property', fn ($pq) => $pq->where('project_id', $filters->projectId));
            });
        }

        return [
            ['label' => 'Property Sales Receipts', 'amount' => (float) (clone $query)->sum('amount')],
        ];
    }

    private static function realEstateBookingValue(DashboardFilterBag $filters): float
    {
        $query = Booking::query();
        $filters->applyDateRange($query, 'booking_date');

        if ($filters->projectId) {
            $query->whereHas('property', fn ($q) => $q->where('project_id', $filters->projectId));
        }

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

    private static function constructionMaterialPurchases(DashboardFilterBag $filters): float
    {
        $query = MaterialStock::query();
        $filters->applyDateRange($query, 'received_date');
        self::applyMaterialProjectScope($query, $filters);

        return (float) (clone $query)
            ->selectRaw('COALESCE(SUM(quantity_received * unit_price), 0) as total')
            ->value('total');
    }

    private static function constructionContractorCosts(DashboardFilterBag $filters): float
    {
        $query = Contractor::query();
        self::applyContractorProjectScope($query, $filters);

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

    private static function constructionContractValue(DashboardFilterBag $filters): float
    {
        $query = Project::query();

        if ($filters->projectId) {
            $query->where('id', $filters->projectId);
        } elseif ($filters->projectStatus) {
            $query->where('status', $filters->projectStatus);
        }

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

    private static function constructionClientPayments(DashboardFilterBag $filters): float
    {
        $query = ProjectPayment::query();
        $filters->applyDateRange($query, 'payment_date');

        if ($filters->projectId) {
            $query->where('project_id', $filters->projectId);
        } elseif ($filters->projectStatus) {
            $query->whereHas('project', fn ($q) => $q->where('status', $filters->projectStatus));
        }

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

    private static function constructionVendorPayments(DashboardFilterBag $filters): float
    {
        $query = VendorPayment::query();
        $filters->applyDateRange($query, 'payment_date');

        if ($filters->projectId) {
            $query->whereHas('materialStock', fn ($q) => $q->where('project_id', $filters->projectId));
        } elseif ($filters->projectStatus) {
            $query->whereHas('materialStock.project', fn ($q) => $q->where('status', $filters->projectStatus));
        }

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

    /**
     * @param  array<int, CrmModule>  $modules
     * @param  array<int, ExpenseCategory>  $categories
     * @return array<int, array{label: string, amount: float}>
     */
    private static function expenseLines(
        DashboardFilterBag $filters,
        array $modules,
        array $categories,
    ): array {
        $moduleValues = array_map(fn (CrmModule $m) => $m->value, $modules);
        $categoryValues = array_map(fn (ExpenseCategory $c) => $c->value, $categories);

        $rows = Expense::query()
            ->whereIn('module', $moduleValues)
            ->whereIn('category', $categoryValues);
        $filters->applyDateRange($rows, 'expense_date');

        if ($filters->projectId) {
            $rows->where('project_id', $filters->projectId);
        } elseif ($filters->projectStatus) {
            $rows->whereHas('project', fn ($q) => $q->where('status', $filters->projectStatus));
        }

        $grouped = $rows
            ->select('category', DB::raw('SUM(amount) as total'))
            ->groupBy('category')
            ->orderByDesc('total')
            ->get();

        $lines = [];
        foreach ($categories as $category) {
            $amount = (float) ($grouped->firstWhere('category', $category->value)?->total ?? 0);
            if ($amount > 0) {
                $lines[] = ['label' => $category->label(), 'amount' => $amount];
            }
        }

        return $lines;
    }

    /**
     * @param  \Illuminate\Database\Eloquent\Builder<\App\Models\MaterialStock>  $query
     */
    private static function applyMaterialProjectScope($query, DashboardFilterBag $filters): void
    {
        if ($filters->projectId) {
            $query->where('project_id', $filters->projectId);
        } elseif ($filters->projectStatus) {
            $query->whereHas('project', fn ($q) => $q->where('status', $filters->projectStatus));
        }
    }

    /**
     * @param  \Illuminate\Database\Eloquent\Builder<\App\Models\Contractor>  $query
     */
    private static function applyContractorProjectScope($query, DashboardFilterBag $filters): void
    {
        if ($filters->projectId) {
            $query->where('project_id', $filters->projectId);
        } elseif ($filters->projectStatus) {
            $query->whereHas('project', fn ($q) => $q->where('status', $filters->projectStatus));
        }
    }

    /**
     * @param  array<int, array{label: string, amount: float}>  $lines
     */
    private static function sumLines(array $lines): float
    {
        return array_sum(array_column($lines, 'amount'));
    }
}
