<?php

namespace App\Services\System;

use App\Enums\OrderStatus;
use App\Enums\Role;
use App\Models\Attendance;
use App\Models\CashAccount;
use App\Models\CashTransaction;
use App\Models\Employee;
use App\Models\EmployeeAdvance;
use App\Models\Expense;
use App\Models\LeaveRequest;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\ProductVariant;
use App\Models\Purchase;
use App\Models\RawMaterialPrice;
use App\Models\User;
use Carbon\Carbon;

class AnalysisService
{
    public function getAttendance(): array
    {
        $totalEmployees = Employee::query()->count();
        $today = Carbon::today();

        $present = Attendance::query()
            ->where('attendance_date', $today)
            ->distinct('employee_id')
            ->count('employee_id');

        $onLeave = LeaveRequest::query()
            ->approved()
            ->where('start_date', '<=', $today)
            ->where('end_date', '>=', $today)
            ->distinct('employee_id')
            ->count('employee_id');

        $absent = max(0, $totalEmployees - $present - $onLeave);

        return [
            'total_employees' => $totalEmployees,
            'percentage' => $totalEmployees > 0 ? round($present / $totalEmployees * 100) : 0,
            'present' => $present,
            'absent' => $absent,
            'on_leave' => $onLeave,
        ];
    }

    public function getMyAttendance(User $user, ?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        $employee = $user->employee;

        if ($employee === null) {
            return [
                'total_days' => 0,
                'present_days' => 0,
                'absent_days' => 0,
                'leave_days' => 0,
                'percentage' => 0,
            ];
        }

        $start = $startDate ?? ($employee->join_date ? Carbon::parse($employee->join_date) : Carbon::today()->startOfMonth());
        $end = $endDate ?? Carbon::today();

        $totalDays = 0;
        $presentDays = 0;
        $current = $start->copy()->startOfDay();

        while ($current->lte($end)) {
            if ($current->isWeekday()) {
                $totalDays++;

                $hasAttendance = Attendance::query()
                    ->where('employee_id', $employee->id)
                    ->whereDate('attendance_date', $current)
                    ->exists();

                if ($hasAttendance) {
                    $presentDays++;
                }
            }

            $current->addDay();
        }

        $leaveDays = LeaveRequest::query()
            ->approved()
            ->where('employee_id', $employee->id)
            ->where('start_date', '<=', $end->toDateString())
            ->where('end_date', '>=', $start->toDateString())
            ->get()
            ->sum(fn ($leave) => max(
                0,
                min($leave->end_date, $end->toDateString())
                - max($leave->start_date, $start->toDateString())
            ) / 86400 + 1);

        $leaveDays = (int) $leaveDays;
        $absentDays = max(0, $totalDays - $presentDays - $leaveDays);
        $percentage = $totalDays > 0 ? round(($presentDays / $totalDays) * 100) : 0;

        return [
            'total_days' => $totalDays,
            'present_days' => $presentDays,
            'absent_days' => $absentDays,
            'leave_days' => $leaveDays,
            'percentage' => $percentage,
        ];
    }

    public function getCashOverview(): array
    {
        $totalBalance = CashAccount::query()->sum('balance');

        $summary = CashTransaction::query()
            ->whereDate('created_at', Carbon::today())
            ->selectRaw("
                COUNT(*) as total_transactions,
                COALESCE(SUM(CASE WHEN type = 'deposit' THEN amount ELSE 0 END), 0) as total_deposit,
                COALESCE(SUM(CASE WHEN type = 'withdrawal' THEN amount ELSE 0 END), 0) as total_withdrawal
            ")
            ->first();

        return [
            'total_balance' => (int) $totalBalance,
            'total_transactions' => (int) ($summary->total_transactions ?? 0),
            'total_deposit' => (int) ($summary->total_deposit ?? 0),
            'total_withdrawal' => (int) ($summary->total_withdrawal ?? 0),
        ];
    }

    public function getTopSuppliers(?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        return Purchase::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate]))
            ->join('suppliers', 'purchases.supplier_id', '=', 'suppliers.id')
            ->selectRaw('suppliers.name, SUM(purchases.total) as total_amount, COUNT(purchases.id) as purchase_count')
            ->groupBy('suppliers.id', 'suppliers.name')
            ->orderByDesc('total_amount')
            ->limit(5)
            ->get()
            ->map(fn ($item) => [
                'name' => $item->name,
                'total_amount' => (int) $item->total_amount,
                'purchase_count' => (int) $item->purchase_count,
            ])
            ->toArray();
    }

    public function getTopCustomers(?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        $user = auth()->user();
        $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
        $isCashier = $user?->hasRole('cashier') ?? false;

        return Order::query()
            ->completed()
            ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
            ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
            ->join('customers', 'orders.customer_id', '=', 'customers.id')
            ->selectRaw('customers.name, SUM(orders.total_amount) as total_amount, COUNT(orders.id) as order_count')
            ->groupBy('customers.id', 'customers.name')
            ->orderByDesc('total_amount')
            ->limit(5)
            ->get()
            ->map(fn ($item) => [
                'name' => $item->name,
                'total_amount' => (int) $item->total_amount,
                'order_count' => (int) $item->order_count,
            ])
            ->toArray();
    }

    public function getRevenueSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        $user = auth()->user();
        $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
        $isCashier = $user?->hasRole('cashier') ?? false;

        $revenueSummary = Order::query()
            ->completed()
            ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
            ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
            ->selectRaw('SUM(total_amount) as total_revenue, SUM(discount) as total_discount, COUNT(*) as total_orders, AVG(total_amount) as avg_order')
            ->first();

        $totalMarketplaceFees = Order::query()
            ->completed()
            ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
            ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
            ->whereNotNull('marketplace_settings_snapshot')
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
            ->get()
            ->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0));

        return [
            'total_revenue' => (int) ($revenueSummary->total_revenue ?? 0),
            'total_discount' => (int) ($revenueSummary->total_discount ?? 0),
            'total_marketplace_fees' => $totalMarketplaceFees,
            'total_deduction' => (int) ($revenueSummary->total_discount ?? 0) + $totalMarketplaceFees,
            'total_orders' => (int) ($revenueSummary->total_orders ?? 0),
            'avg_order' => (int) ($revenueSummary->avg_order ?? 0),
        ];
    }

    public function getMonthlyRevenue(?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        $user = auth()->user();
        $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
        $isCashier = $user?->hasRole('cashier') ?? false;

        $query = Order::query()->completed()
            ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
            ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id));
        $feeQuery = Order::query()->completed()
            ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
            ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
            ->whereNotNull('marketplace_settings_snapshot');

        if ($startDate && $endDate) {
            $query->whereBetween('orders.created_at', [$startDate, $endDate]);
            $feeQuery->whereBetween('orders.created_at', [$startDate, $endDate]);
        }

        $monthlyData = $query
            ->selectRaw("
                DATE_FORMAT(orders.created_at, '%Y-%m') as month_key,
                SUM(total_amount) as total_revenue,
                SUM(discount) as total_discount
            ")
            ->groupBy('month_key')
            ->orderBy('month_key')
            ->get();

        $monthlyFees = $feeQuery
            ->selectRaw("DATE_FORMAT(orders.created_at, '%Y-%m') as month_key")
            ->get()
            ->groupBy('month_key')
            ->map(fn ($orders) => $orders->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0)));

        if ($monthlyData->isEmpty()) {
            return [];
        }

        $start = $startDate ?? Carbon::parse($monthlyData->first()->month_key.'-01');
        $end = $endDate ?? Carbon::parse($monthlyData->last()->month_key.'-01')->endOfMonth();

        $result = [];
        $current = $start->copy()->startOfMonth();
        while ($current->lte($end)) {
            $key = $current->format('Y-m');
            $monthLabel = $current->locale('id')->translatedFormat('M Y');

            $revenue = $monthlyData->firstWhere('month_key', $key);
            $fees = $monthlyFees->get($key, 0);
            $discount = (int) ($revenue->total_discount ?? 0);
            $deduction = $discount + $fees;

            $result[] = [
                'month' => $monthLabel,
                'total' => (int) ($revenue->total_revenue ?? 0),
                'net' => (int) ($revenue->total_revenue ?? 0) - $deduction,
                'deduction' => $deduction,
            ];

            $current->addMonth();
        }

        return $result;
    }

    public function getExpenseSummary(?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        $purchase = Purchase::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate]))
            ->selectRaw('COALESCE(SUM(total), 0) as total')
            ->first();

        $expenses = Expense::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate]))
            ->selectRaw('COALESCE(SUM(amount), 0) as total')
            ->first();

        $employeeAdvance = EmployeeAdvance::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate]))
            ->selectRaw('COALESCE(SUM(amount), 0) as total')
            ->first();

        $purchaseTotal = (int) ($purchase->total ?? 0);
        if (auth()->user()?->hasRole(Role::ADMIN_TOKO->value)) {
            $purchaseTotal = 0;
        }
        $expenseTotal = (int) ($expenses->total ?? 0);
        $advanceTotal = (int) ($employeeAdvance->total ?? 0);

        return [
            'total' => $purchaseTotal + $expenseTotal + $advanceTotal,
            'purchase_total' => $purchaseTotal,
            'expense_total' => $expenseTotal,
            'advance_total' => $advanceTotal,
        ];
    }

    public function getMonthlyExpense(?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        $purchases = Purchase::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate]))
            ->selectRaw("DATE_FORMAT(purchases.created_at, '%Y-%m') as month_key, COALESCE(SUM(total), 0) as total")
            ->groupBy('month_key')
            ->orderBy('month_key')
            ->get();

        $expenses = Expense::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate]))
            ->selectRaw("DATE_FORMAT(expenses.created_at, '%Y-%m') as month_key, COALESCE(SUM(amount), 0) as total")
            ->groupBy('month_key')
            ->orderBy('month_key')
            ->get();

        $advances = EmployeeAdvance::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate]))
            ->selectRaw("DATE_FORMAT(employee_advances.created_at, '%Y-%m') as month_key, COALESCE(SUM(amount), 0) as total")
            ->groupBy('month_key')
            ->orderBy('month_key')
            ->get();

        $allMonths = collect()
            ->merge($purchases->pluck('month_key'))
            ->merge($expenses->pluck('month_key'))
            ->merge($advances->pluck('month_key'))
            ->unique()
            ->sort()
            ->values();

        if ($allMonths->isEmpty()) {
            return [];
        }

        $start = $startDate ?? Carbon::parse($allMonths->first().'-01');
        $end = $endDate ?? Carbon::parse($allMonths->last().'-01')->endOfMonth();

        $result = [];
        $current = $start->copy()->startOfMonth();
        while ($current->lte($end)) {
            $key = $current->format('Y-m');
            $monthLabel = $current->locale('id')->translatedFormat('M Y');

            $purchaseAmount = (int) ($purchases->firstWhere('month_key', $key)->total ?? 0);
            if (auth()->user()?->hasRole(Role::ADMIN_TOKO->value)) {
                $purchaseAmount = 0;
            }
            $expenseAmount = (int) ($expenses->firstWhere('month_key', $key)->total ?? 0);
            $advanceAmount = (int) ($advances->firstWhere('month_key', $key)->total ?? 0);

            $result[] = [
                'month' => $monthLabel,
                'total' => $purchaseAmount + $expenseAmount + $advanceAmount,
                'purchase' => $purchaseAmount,
                'expense' => $expenseAmount,
                'advance' => $advanceAmount,
            ];

            $current->addMonth();
        }

        return $result;
    }

    public function getProfitMetrics(?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        $user = auth()->user();
        $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
        $isCashier = $user?->hasRole('cashier') ?? false;

        $orderQuery = Order::query()->completed()
            ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
            ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id));
        if ($startDate && $endDate) {
            $orderQuery->whereBetween('orders.created_at', [$startDate, $endDate]);
        }

        $revenueData = (clone $orderQuery)
            ->selectRaw('
                COUNT(*) as total_orders,
                SUM(total_amount) as total_revenue,
                SUM(discount) as total_discount,
                SUM(subtotal) as total_subtotal
            ')
            ->first();

        $totalRevenue = (int) ($revenueData->total_revenue ?? 0);
        $totalDiscount = (int) ($revenueData->total_discount ?? 0);
        $totalSubtotal = (int) ($revenueData->total_subtotal ?? 0);
        $totalOrders = (int) ($revenueData->total_orders ?? 0);

        $marketplaceFees = (clone $orderQuery)
            ->whereNotNull('marketplace_settings_snapshot')
            ->get()
            ->sum(fn ($order) => (int) ($order->marketplace_settings_snapshot['total_fee_amount'] ?? 0));

        $itemsData = OrderItem::query()
            ->join('orders', 'order_items.order_id', '=', 'orders.id')
            ->where('orders.status', OrderStatus::COMPLETED)
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
            ->selectRaw('
                SUM(order_items.quantity) as total_qty,
                COUNT(order_items.id) as total_items
            ')
            ->first();

        $totalQty = (int) ($itemsData->total_qty ?? 0);
        $totalItems = (int) ($itemsData->total_items ?? 0);

        // HPP from cutting_result_prices
        $hpp = OrderItem::query()
            ->join('orders', 'order_items.order_id', '=', 'orders.id')
            ->leftJoin('cutting_result_prices', 'order_items.product_variant_id', '=', 'cutting_result_prices.product_variant_id')
            ->where('orders.status', OrderStatus::COMPLETED)
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
            ->selectRaw('COALESCE(SUM(order_items.quantity * cutting_result_prices.cost_per_unit), 0) as total_hpp')
            ->value('total_hpp');

        $totalHpp = (int) ($hpp ?? 0);

        // Expenses
        $purchaseTotal = Purchase::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('purchases.created_at', [$startDate, $endDate]))
            ->sum('total');

        if (auth()->user()?->hasRole(Role::ADMIN_TOKO->value)) {
            $purchaseTotal = 0;
        }

        $expenseTotal = Expense::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('expenses.created_at', [$startDate, $endDate]))
            ->sum('amount');

        $advanceTotal = EmployeeAdvance::query()
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('employee_advances.created_at', [$startDate, $endDate]))
            ->sum('amount');

        $totalExpenses = (int) $purchaseTotal + (int) $expenseTotal + (int) $advanceTotal;

        $labaKotor = $totalRevenue - $totalHpp - $totalDiscount - $marketplaceFees;
        $labaBersih = $labaKotor - $totalExpenses;
        $profitMargin = $totalRevenue > 0 ? round(($labaBersih / $totalRevenue) * 100, 1) : 0;
        $aov = $totalOrders > 0 ? (int) round($totalRevenue / $totalOrders) : 0;
        $itemsPerTransaction = $totalOrders > 0 ? round($totalItems / $totalOrders, 1) : 0;

        return [
            'total_orders' => $totalOrders,
            'total_products_sold' => $totalQty,
            'hpp' => $totalHpp,
            'laba_kotor' => $labaKotor,
            'laba_bersih' => $labaBersih,
            'profit_margin' => $profitMargin,
            'aov' => $aov,
            'items_per_transaction' => $itemsPerTransaction,
        ];
    }

    public function getRawMaterialStock(): array
    {
        $prices = RawMaterialPrice::query()
            ->join('raw_materials', 'raw_material_prices.raw_material_id', '=', 'raw_materials.id')
            ->selectRaw('
                raw_materials.unit,
                SUM(raw_material_prices.stock) as total_stock,
                SUM(raw_material_prices.stock * raw_material_prices.price) as total_value
            ')
            ->groupBy('raw_materials.unit')
            ->get()
            ->keyBy('unit');

        $totalStock = (float) $prices->sum('total_stock');
        $totalValue = (int) $prices->sum('total_value');

        return [
            'total_stock' => round($totalStock, 2),
            'total_value' => $totalValue,
            'by_unit' => [
                'yard' => round((float) ($prices->get('yard')->total_stock ?? 0), 2),
                'meter' => round((float) ($prices->get('meter')->total_stock ?? 0), 2),
                'kilogram' => round((float) ($prices->get('kilogram')->total_stock ?? 0), 2),
            ],
        ];
    }

    public function getProductStock(): array
    {
        $variants = ProductVariant::query()
            ->join('products', 'product_variants.product_id', '=', 'products.id')
            ->selectRaw('
                SUM(product_variants.stock) as total_stock,
                SUM(product_variants.reject_stock) as total_reject,
                SUM(product_variants.retail_stock) as total_retail,
                COUNT(product_variants.id) as total_variants,
                COUNT(DISTINCT products.id) as total_products
            ')
            ->first();

        $totalValue = \DB::table('product_prices')
            ->join('product_variants', 'product_prices.variant_id', '=', 'product_variants.id')
            ->selectRaw('SUM(product_variants.stock * product_prices.price) as total')
            ->value('total');

        $totalCategories = \DB::table('product_categories')
            ->distinct('category_id')
            ->count('category_id');

        return [
            'total_stock' => (int) ($variants->total_stock ?? 0),
            'total_reject' => (int) ($variants->total_reject ?? 0),
            'total_retail' => (int) ($variants->total_retail ?? 0),
            'total_value' => (int) ($totalValue ?? 0),
            'total_products' => (int) ($variants->total_products ?? 0),
            'total_variants' => (int) ($variants->total_variants ?? 0),
            'total_categories' => (int) $totalCategories,
        ];
    }

    public function getBusyHours(?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        $user = auth()->user();
        $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
        $isCashier = $user?->hasRole('cashier') ?? false;

        $hourlyData = Order::query()
            ->when($isMarketing, fn ($q) => $q->where('marketing_id', $user->id))
            ->when($isCashier, fn ($q) => $q->where('created_by_id', $user->id))
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
            ->selectRaw('HOUR(orders.created_at) as hour, COUNT(*) as order_count')
            ->groupBy('hour')
            ->orderBy('hour')
            ->get()
            ->keyBy('hour');

        $result = [];
        for ($h = 0; $h < 24; $h++) {
            $result[] = [
                'hour' => str_pad($h, 2, '0', STR_PAD_LEFT).':00',
                'orders' => (int) ($hourlyData->get($h)->order_count ?? 0),
            ];
        }

        return $result;
    }

    public function getTopProducts(?Carbon $startDate = null, ?Carbon $endDate = null): array
    {
        $user = auth()->user();
        $isMarketing = $user?->hasAnyRole(['marketing-offline', 'marketing-online']) ?? false;
        $isCashier = $user?->hasRole('cashier') ?? false;

        return OrderItem::query()
            ->join('orders', 'order_items.order_id', '=', 'orders.id')
            ->join('product_variants', 'order_items.product_variant_id', '=', 'product_variants.id')
            ->join('products', 'product_variants.product_id', '=', 'products.id')
            ->where('orders.status', OrderStatus::COMPLETED)
            ->when($isMarketing, fn ($q) => $q->where('orders.marketing_id', $user->id))
            ->when($isCashier, fn ($q) => $q->where('orders.created_by_id', $user->id))
            ->when($startDate && $endDate, fn ($q) => $q->whereBetween('orders.created_at', [$startDate, $endDate]))
            ->selectRaw("CONCAT(products.name, ' - ', product_variants.name) as full_name, SUM(order_items.quantity) as total_qty, SUM(order_items.subtotal) as total_revenue")
            ->groupBy('order_items.product_variant_id', 'products.name', 'product_variants.name')
            ->orderByDesc('total_qty')
            ->limit(5)
            ->get()
            ->map(fn ($item) => [
                'name' => $item->full_name,
                'total_qty' => (int) $item->total_qty,
                'total_revenue' => (int) $item->total_revenue,
            ])
            ->toArray();
    }

    public function isManager(?User $user): bool
    {
        return $user?->hasAnyRole([Role::OWNER->value, Role::DEVELOPER->value, Role::DIREKTUR->value]) ?? false;
    }
}
