<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Location;
use App\Models\Order;
use App\Models\User;
use App\Services\Dashboard\OverviewDashboardService;
use App\Services\Dashboard\StoreDashboardService;
use App\Services\Dashboard\WarehouseDashboardService;
use App\Services\LocationService;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\View;

class DashboardController extends Controller
{
    public function __construct(
        protected LocationService $locationService,
        protected StoreDashboardService $storeDashboard,
        protected WarehouseDashboardService $warehouseDashboard,
        protected OverviewDashboardService $overviewDashboard
    ) {
    }

    public function index(Request $request): View
    {
        $user = auth()->user();
        $allLocations = $this->locationService->accessibleLocations($user);
        $storeLocations = $this->locationService->accessibleStores($user);
        $warehouseLocations = $this->locationService->accessibleWarehouses($user);
        $canViewAllShops = $this->locationService->userCanViewAllLocations($user);

        $modes = $this->locationService->dashboardModesFor($user);
        $requestedTab = $request->input('tab');
        $tab = is_string($requestedTab) && in_array($requestedTab, $modes, true)
            ? $requestedTab
            : $this->locationService->defaultDashboardTab($user, $modes);
        session(['dashboard_tab' => $tab]);

        $locationsForTab = match ($tab) {
            'warehouse' => $warehouseLocations->isNotEmpty() ? $warehouseLocations : $allLocations->where('type', Location::TYPE_WAREHOUSE)->values(),
            'store' => $storeLocations->isNotEmpty() ? $storeLocations : $allLocations->where('type', Location::TYPE_STORE)->values(),
            default => $allLocations,
        };

        // Admins on store/warehouse tabs: if type filter empty, use all of that type from DB
        if ($canViewAllShops && $locationsForTab->isEmpty() && in_array($tab, ['store', 'warehouse'], true)) {
            $type = $tab === 'warehouse' ? Location::TYPE_WAREHOUSE : Location::TYPE_STORE;
            $locationsForTab = Location::query()->active()->where('type', $type)->orderBy('priority')->get();
        }

        $locationId = $this->resolveTabLocationId(
            $user,
            $request->input('location_id', 'current'),
            $tab,
            $canViewAllShops,
            $locationsForTab
        );

        $selectedLocation = $locationId ? Location::query()->find($locationId) : null;

        $dashboardLocationLabel = match ($tab) {
            'warehouse' => $selectedLocation?->name ?? ($canViewAllShops ? 'All warehouses' : 'Warehouse'),
            'overview' => 'All locations',
            default => $selectedLocation?->name ?? ($canViewAllShops ? 'All stores' : 'Store'),
        };

        $data = [
            'tab' => $tab,
            'dashboardModes' => $modes,
            'locations' => $locationsForTab,
            'locationId' => $locationId,
            'selectedLocation' => $selectedLocation,
            'canViewAllShops' => $canViewAllShops,
            'dashboardLocationLabel' => $dashboardLocationLabel,
            'allLocations' => $allLocations,
        ];

        if ($tab === 'warehouse') {
            $data = array_merge($data, $this->warehouseDashboard->build($locationId, $locationsForTab));
        } elseif ($tab === 'overview') {
            $data = array_merge($data, $this->overviewDashboard->build($user, $allLocations));
        } else {
            $data = array_merge($data, $this->storeDashboard->build($locationId));
        }

        return view('admin.dashboard', $data);
    }

    /**
     * Salesman performance chart data (line chart: one line per staff, POS only).
     */
    public function salesmanChartData(Request $request): JsonResponse
    {
        $user = auth()->user();
        $canViewAllShops = $this->locationService->userCanViewAllLocations($user);
        $hasLocationColumn = Schema::hasColumn('orders', 'fulfillment_location_id');

        $locationId = null;
        $requestedLocation = $request->input('location_id');

        if ($requestedLocation === 'all' && $canViewAllShops) {
            $locationId = null;
        } elseif ($requestedLocation && $requestedLocation !== 'all') {
            $candidate = (int) $requestedLocation;
            $locationId = $this->locationService->userCanAccessLocation($user, $candidate)
                ? $candidate
                : $this->locationService->resolveLocationIdForUser($user);
        } else {
            $locationId = $canViewAllShops
                ? $this->locationService->currentLocationId()
                : $this->locationService->resolveLocationIdForUser($user);
        }

        $period = $request->input('period', 'week');
        $startDate = $request->input('start_date', now()->subDays(6)->format('Y-m-d'));
        $endDate = $request->input('end_date', now()->format('Y-m-d'));

        switch ($period) {
            case 'day':
                $startDateTime = now()->startOfDay();
                $endDateTime = now()->endOfDay();
                break;
            case 'week':
                $startDateTime = now()->copy()->startOfWeek();
                $endDateTime = now()->copy()->endOfWeek();
                break;
            case 'month':
                $startDateTime = now()->copy()->startOfMonth();
                $endDateTime = now()->copy()->endOfMonth();
                break;
            case 'year':
                $startDateTime = now()->copy()->startOfYear();
                $endDateTime = now()->copy()->endOfYear();
                break;
            default:
                $startDateTime = Carbon::parse($startDate)->startOfDay();
                $endDateTime = Carbon::parse($endDate)->endOfDay();
        }

        $staff = User::whereIn('user_type', ['admin', 'staff'])
            ->select('id', 'name')
            ->orderBy('name')
            ->get();

        $labels = [];
        $bucketKey = 'bucket';
        $dateFormat = 'Y-m-d';

        if ($period === 'day') {
            for ($h = 0; $h < 24; $h++) {
                $labels[] = sprintf('%02d:00', $h);
            }
        } else {
            $cursor = $startDateTime->copy();
            while ($cursor->lte($endDateTime)) {
                if ($period === 'year') {
                    $labels[] = $cursor->format('M');
                    $cursor->addMonth();
                } else {
                    $labels[] = $cursor->format('M j');
                    $cursor->addDay();
                }
            }
        }

        $baseQuery = Order::query()
            ->where('source', 'pos')
            ->where('payment_status', Order::PAYMENT_PAID)
            ->whereNotIn('status', [Order::STATUS_CANCELLED, Order::STATUS_RETURNED])
            ->whereBetween('created_at', [$startDateTime, $endDateTime]);

        if ($locationId && $hasLocationColumn) {
            $baseQuery->where('fulfillment_location_id', $locationId);
        }

        if ($period === 'day') {
            $raw = $baseQuery->select('staff_id', DB::raw('HOUR(created_at) as '.$bucketKey), DB::raw('SUM(total_payable) as total'))
                ->groupBy('staff_id', $bucketKey)
                ->get();
        } elseif ($period === 'year') {
            $raw = $baseQuery->select('staff_id', DB::raw('MONTH(created_at) as '.$bucketKey), DB::raw('SUM(total_payable) as total'))
                ->groupBy('staff_id', $bucketKey)
                ->get();
        } else {
            $raw = $baseQuery->select('staff_id', DB::raw('DATE(created_at) as '.$bucketKey), DB::raw('SUM(total_payable) as total'))
                ->groupBy('staff_id', $bucketKey)
                ->get();
        }

        $byStaff = $raw->groupBy('staff_id');

        $colorPalette = [
            'rgb(59, 130, 246)', 'rgb(16, 185, 129)', 'rgb(245, 158, 11)',
            'rgb(239, 68, 68)', 'rgb(139, 92, 246)', 'rgb(236, 72, 153)',
            'rgb(20, 184, 166)', 'rgb(251, 146, 60)',
        ];

        $datasets = [];
        $idx = 0;
        foreach ($staff as $s) {
            $sid = $s->id;
            $name = $s->name;
            $bucketValues = $byStaff->get($sid, collect())->keyBy($bucketKey);

            if ($period === 'day') {
                $data = [];
                for ($h = 0; $h < 24; $h++) {
                    $data[] = (float) ($bucketValues->get($h)?->total ?? 0);
                }
            } elseif ($period === 'year') {
                $data = [];
                $cursor = $startDateTime->copy();
                while ($cursor->lte($endDateTime)) {
                    $m = (int) $cursor->format('n');
                    $data[] = (float) ($bucketValues->get($m)?->total ?? 0);
                    $cursor->addMonth();
                }
            } else {
                $data = [];
                $cursor = $startDateTime->copy();
                while ($cursor->lte($endDateTime)) {
                    $d = $cursor->format($dateFormat);
                    $data[] = (float) ($bucketValues->get($d)?->total ?? 0);
                    $cursor->addDay();
                }
            }

            if (array_sum($data) <= 0) {
                continue;
            }

            $color = $colorPalette[$idx % count($colorPalette)];
            $bg = str_replace(')', ', 0.1)', str_replace('rgb', 'rgba', $color));
            $datasets[] = [
                'staff_id' => $sid,
                'staff_name' => $name,
                'data' => $data,
                'borderColor' => $color,
                'backgroundColor' => $bg,
            ];
            $idx++;
        }

        return response()->json([
            'labels' => $labels,
            'datasets' => $datasets,
            'location_id' => $locationId,
        ]);
    }

    protected function resolveTabLocationId(
        User $user,
        mixed $requestedLocation,
        string $tab,
        bool $canViewAllShops,
        $locationsForTab
    ): ?int {
        if ($tab === 'overview') {
            if ($canViewAllShops) {
                $this->locationService->clearCurrentLocation();
            }

            return null;
        }

        $allowedIds = $locationsForTab->pluck('id')->map(fn ($id) => (int) $id)->all();

        if ($requestedLocation === 'all' && $canViewAllShops) {
            $this->locationService->clearCurrentLocation();

            return null;
        }

        $resolved = null;
        if ($requestedLocation === 'current' || $requestedLocation === null || $requestedLocation === '') {
            $candidate = $this->locationService->resolveLocationIdForUser($user);
            if ($candidate && in_array((int) $candidate, $allowedIds, true)) {
                $resolved = (int) $candidate;
            } elseif ($tab === 'warehouse') {
                $resolved = $this->locationService->preferredStockLocationId($user);
                if ($resolved && ! in_array((int) $resolved, $allowedIds, true)) {
                    $resolved = $locationsForTab->first()?->id;
                }
            } else {
                $resolved = $locationsForTab->first()?->id;
            }
        } else {
            $candidate = (int) $requestedLocation;
            if ($this->locationService->userCanAccessLocation($user, $candidate)
                && (empty($allowedIds) || in_array($candidate, $allowedIds, true))) {
                $resolved = $candidate;
            }
        }

        if (! $resolved && ! $canViewAllShops) {
            $resolved = $locationsForTab->first()?->id
                ? (int) $locationsForTab->first()->id
                : $this->locationService->resolveLocationIdForUser($user);
        }

        if ($resolved) {
            // Keep topbar/session in sync when the user (or URL) intentionally
            // selected this location. Do not clobber a freshly switched session
            // location with a tab fallback (e.g. store tab while session is WH).
            $sessionId = $this->locationService->currentLocationId();
            $requestedExplicitly = ! in_array($requestedLocation, [null, '', 'current'], true);

            if ($requestedExplicitly || $sessionId === null || (int) $sessionId === (int) $resolved) {
                $this->locationService->setCurrentLocation((int) $resolved);
            }
        }

        return $resolved ? (int) $resolved : null;
    }
}