<?php

namespace App\Services;

use App\Models\Company;
use App\Models\Location;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class LocationService
{
    public function setCurrentLocation(int $locationId): void
    {
        session()->put('current_location_id', $locationId);
    }

    public function currentLocationId(): ?int
    {
        $id = session('current_location_id');

        return $id !== null && $id !== '' ? (int) $id : null;
    }

    public function currentLocation(): ?Location
    {
        $id = $this->currentLocationId();

        return $id ? Location::query()->find($id) : null;
    }

    /**
     * Resolved admin location for the user (session / default / assignment), not raw session only.
     */
    public function resolveCurrentLocation(?User $user = null): ?Location
    {
        $id = $this->resolveLocationIdForUser($user);

        return $id ? Location::query()->find($id) : null;
    }

    public function currentLocationIsWarehouse(?User $user = null): bool
    {
        $location = $this->resolveCurrentLocation($user);

        return $location !== null && $location->isWarehouse();
    }

    /**
     * Catalogue masters (products / categories / units) may be written only at a warehouse.
     */
    public function catalogueWritableForUser(?User $user = null): bool
    {
        return $this->currentLocationIsWarehouse($user);
    }

    public function clearCurrentLocation(): void
    {
        session()->forget('current_location_id');
    }

    public function defaultStore(): ?Location
    {
        return Location::defaultStore();
    }

    public function centralWarehouse(): ?Location
    {
        return Location::centralWarehouse();
    }

    public function resolveLocationIdForUser(?User $user = null): ?int
    {
        $user = $user ?? auth()->user();
        if (!$user) {
            return $this->defaultStore()?->id;
        }

        if ($sessionId = $this->currentLocationId()) {
            if ($this->userCanAccessLocation($user, $sessionId)) {
                return $sessionId;
            }
        }

        if ($user->default_location_id && $this->userCanAccessLocation($user, $user->default_location_id)) {
            return $user->default_location_id;
        }

        $assigned = $user->stores()->where('locations.is_active', true)->orderByDesc('store_user.is_default')->first();
        if ($assigned) {
            return $assigned->id;
        }

        if ($user->isAdmin() || $user->hasRole('admin') || $user->hasRole('super_admin')) {
            return $this->defaultStore()?->id;
        }

        return null;
    }

    public function userCanAccessLocation(User $user, int $locationId): bool
    {
        if ($user->isAdmin() || $user->hasRole('admin') || $user->hasRole('super_admin')) {
            return Location::query()->whereKey($locationId)->where('is_active', true)->exists();
        }

        return $user->stores()->where('locations.id', $locationId)->where('locations.is_active', true)->exists();
    }

    public function userCanViewAllLocations(User $user): bool
    {
        return $user->isAdmin() || $user->hasRole('admin') || $user->hasRole('super_admin');
    }

    /**
     * Resolve a location the user may use. If a specific location is requested
     * and they cannot access it, abort 403 by default (do not silently fall back).
     */
    public function resolveAuthorizedLocationId(User $user, mixed $requested = null, bool $abortOnDenied = true): ?int
    {
        if ($requested !== null && $requested !== '' && (int) $requested > 0) {
            $locationId = (int) $requested;
            if (!$this->userCanAccessLocation($user, $locationId)) {
                if ($abortOnDenied) {
                    abort(403, 'You do not have access to this store.');
                }

                return null;
            }

            return $locationId;
        }

        $locationId = $this->resolveLocationIdForUser($user) ?: $this->defaultStore()?->id;
        if (!$locationId) {
            return null;
        }

        if (!$this->userCanAccessLocation($user, (int) $locationId)) {
            return null;
        }

        return (int) $locationId;
    }

    /**
     * Filter/report location: admins may omit location_id (all shops);
     * staff always resolve to an accessible store.
     */
    public function resolveAuthorizedFilterLocationId(User $user, mixed $requested = null): ?int
    {
        if ($requested !== null && $requested !== '' && (int) $requested > 0) {
            return $this->resolveAuthorizedLocationId($user, $requested);
        }

        if ($this->userCanViewAllLocations($user)) {
            return null;
        }

        return $this->resolveAuthorizedLocationId($user, null);
    }

    public function accessibleLocations(User $user): Collection
    {
        if ($this->userCanViewAllLocations($user)) {
            return Location::query()->active()->orderBy('type')->orderBy('priority')->get();
        }

        return $user->stores()->where('locations.is_active', true)->orderBy('type')->orderBy('priority')->get();
    }

    public function accessibleStores(User $user): Collection
    {
        return $this->accessibleLocations($user)
            ->filter(fn (Location $l) => $l->type === Location::TYPE_STORE)
            ->values();
    }

    public function accessibleWarehouses(User $user): Collection
    {
        return $this->accessibleLocations($user)
            ->filter(fn (Location $l) => $l->type === Location::TYPE_WAREHOUSE)
            ->values();
    }

    /**
     * Dashboard tabs the user may open: store | warehouse | overview.
     *
     * @return list<string>
     */
    public function dashboardModesFor(User $user): array
    {
        $stores = $this->accessibleStores($user);
        $warehouses = $this->accessibleWarehouses($user);
        $isAdmin = $this->userCanViewAllLocations($user);

        $modes = [];

        $canStoreTab = $isAdmin
            || $user->can('dashboard.store.view')
            || $user->can('dashboard.view-kpis')
            || $stores->isNotEmpty();

        $canWarehouseTab = $isAdmin
            || $user->can('dashboard.warehouse.view')
            || $user->canAny([
                'inventory.balances.view',
                'transfers.view',
                'purchase-orders.view',
            ])
            || $warehouses->isNotEmpty();

        $canOverviewTab = $isAdmin || $user->can('dashboard.overview.view');

        if ($canStoreTab && ($isAdmin || $stores->isNotEmpty())) {
            $modes[] = 'store';
        }
        if ($canWarehouseTab && ($isAdmin || $warehouses->isNotEmpty())) {
            $modes[] = 'warehouse';
        }
        if ($canOverviewTab) {
            $modes[] = 'overview';
        }

        if ($modes === []) {
            if ($warehouses->isNotEmpty()) {
                $modes[] = 'warehouse';
            } elseif ($stores->isNotEmpty()) {
                $modes[] = 'store';
            } else {
                $modes[] = 'store';
            }
        }

        return $modes;
    }

    public function defaultDashboardTab(User $user, ?array $modes = null): string
    {
        $modes = $modes ?? $this->dashboardModesFor($user);
        $sessionTab = session('dashboard_tab');
        if (is_string($sessionTab) && in_array($sessionTab, $modes, true)) {
            return $sessionTab;
        }

        $currentId = $this->resolveLocationIdForUser($user);
        $current = $currentId ? Location::query()->find($currentId) : null;

        if ($current?->isWarehouse() && in_array('warehouse', $modes, true)) {
            return 'warehouse';
        }
        if ($current?->isStore() && in_array('store', $modes, true)) {
            return 'store';
        }

        $warehouses = $this->accessibleWarehouses($user);
        $stores = $this->accessibleStores($user);
        if ($warehouses->isNotEmpty() && $stores->isEmpty() && in_array('warehouse', $modes, true)) {
            return 'warehouse';
        }
        if ($stores->isNotEmpty() && in_array('store', $modes, true)) {
            return 'store';
        }

        foreach (['store', 'warehouse', 'overview'] as $preferred) {
            if (in_array($preferred, $modes, true)) {
                return $preferred;
            }
        }

        return $modes[0] ?? 'store';
    }

    /**
     * Prefer warehouse when the user can access one (for restock / receiving defaults).
     */
    public function preferredStockLocationId(User $user): ?int
    {
        $locations = $this->accessibleLocations($user);
        $warehouse = $locations->firstWhere('type', Location::TYPE_WAREHOUSE);

        return $warehouse?->id
            ?? $this->centralWarehouse()?->id
            ?? $this->resolveLocationIdForUser($user)
            ?? $locations->first()?->id;
    }

    /**
     * Warehouses the user may restock into (intake is warehouse-only).
     */
    public function restockWarehouseLocations(User $user): Collection
    {
        return $this->accessibleLocations($user)
            ->filter(fn (Location $location) => $location->isWarehouse())
            ->values();
    }

    /**
     * Resolve a warehouse location_id for restock. Store IDs are rejected.
     */
    public function resolveWarehouseRestockLocationId(User $user, mixed $requested = null): int
    {
        $warehouses = $this->restockWarehouseLocations($user);
        if ($warehouses->isEmpty()) {
            $central = $this->centralWarehouse();
            if ($central && $this->userCanAccessLocation($user, (int) $central->id)) {
                $warehouses = collect([$central]);
            }
        }

        if ($warehouses->isEmpty()) {
            abort(403, 'No warehouse is available for restock. Switch to a warehouse location.');
        }

        if ($requested !== null && $requested !== '' && (int) $requested > 0) {
            $locationId = (int) $requested;
            $match = $warehouses->firstWhere('id', $locationId);
            if (!$match) {
                abort(422, 'Restock is only allowed into a warehouse. Stock is received at warehouse, then transferred to stores.');
            }

            return $locationId;
        }

        $preferred = $this->preferredStockLocationId($user);
        if ($preferred && $warehouses->contains(fn (Location $l) => (int) $l->id === (int) $preferred)) {
            return (int) $preferred;
        }

        return (int) $warehouses->first()->id;
    }

    /**
     * Destinations allowed when creating a transfer.
     * Warehouse staff may ship to any active store/warehouse even if not assigned there.
     */
    public function transferDestinationLocations(User $user): Collection
    {
        if ($this->userCanViewAllLocations($user)) {
            return Location::query()->active()->orderBy('type')->orderBy('priority')->get();
        }

        $accessible = $this->accessibleLocations($user);
        $hasWarehouse = $accessible->contains(fn (Location $l) => $l->type === Location::TYPE_WAREHOUSE);

        if ($hasWarehouse) {
            return Location::query()->active()->orderBy('type')->orderBy('priority')->get();
        }

        return $accessible;
    }

    /**
     * Staff must access FROM. Destination: any active if FROM is their warehouse; else must access TO too.
     */
    public function assertCanCreateTransfer(User $user, int $fromLocationId, int $toLocationId): void
    {
        $this->resolveAuthorizedLocationId($user, $fromLocationId);

        if ($this->userCanViewAllLocations($user)) {
            if (!$this->userCanAccessLocation($user, $toLocationId)) {
                abort(403, 'Invalid destination location.');
            }

            return;
        }

        $from = Location::query()->find($fromLocationId);
        if ($from && $from->type === Location::TYPE_WAREHOUSE) {
            $ok = Location::query()->whereKey($toLocationId)->where('is_active', true)->exists();
            if (!$ok) {
                abort(403, 'Invalid destination location.');
            }

            return;
        }

        $this->resolveAuthorizedLocationId($user, $toLocationId);
    }

    public function onlineFulfillmentLocations(): Collection
    {
        return Location::query()->fulfillsOnline()->orderBy('priority')->get();
    }

    /**
     * Courier pickup settings for a fulfillment location (fallback to global Delhivery config).
     *
     * @return array{pickup_name:?string, warehouse_pin:?string, address_line1:?string, phone:?string, city:?string, state:?string, pincode:?string}
     */
    public function courierPickupSettings(?int $locationId = null): array
    {
        $location = $locationId ? Location::query()->with('settings')->find($locationId) : null;

        $pickupName = $location?->getSetting('pickup_name')
            ?: ($location?->name)
            ?: config('services.delhivery.pickup_name');

        $warehousePin = $location?->getSetting('warehouse_pin')
            ?: ($location?->pincode)
            ?: config('services.delhivery.warehouse_pin');

        return [
            'pickup_name' => $pickupName,
            'warehouse_pin' => $warehousePin,
            'address_line1' => $location?->address_line1 ?: config('services.delhivery.address_line1'),
            'phone' => $location?->phone ?: config('services.delhivery.phone'),
            'city' => $location?->city ?: config('services.delhivery.city'),
            'state' => $location?->state ?: config('services.delhivery.state'),
            'pincode' => $warehousePin,
        ];
    }

    /**
     * Active location pickup_name values for Delhivery pickup requests.
     *
     * @return array<int, string>
     */
    public function delhiveryPickupLocationNames(): array
    {
        $names = Location::query()
            ->active()
            ->with('settings')
            ->get()
            ->map(function (Location $location) {
                return $location->getSetting('pickup_name') ?: $location->name;
            })
            ->filter()
            ->unique()
            ->values()
            ->all();

        $global = config('services.delhivery.pickup_name');
        if ($global && !in_array($global, $names, true)) {
            array_unshift($names, $global);
        }

        return $names;
    }

    public function pickupStores(): Collection
    {
        return Location::query()->stores()->allowsPickup()->orderBy('priority')->get();
    }

    public function ensureCompanyAndDefaults(): Company
    {
        return DB::transaction(function () {
            $company = Company::primary();
            if (!$company) {
                $company = Company::create([
                    'name' => 'Shiva Jewellers',
                    'legal_name' => 'Shiva Jewellers',
                    'phone' => '9966337448',
                    'address' => 'Warangal',
                    'city' => 'Warangal',
                    'state' => 'Telangana',
                    'pincode' => '506002',
                    'country' => 'IN',
                    'is_active' => true,
                ]);
            }

            $warehouse = Location::centralWarehouse();
            if (!$warehouse) {
                $warehouse = Location::create([
                    'company_id' => $company->id,
                    'code' => 'WH-CENTRAL',
                    'name' => 'Central Warehouse',
                    'type' => Location::TYPE_WAREHOUSE,
                    'city' => 'Warangal',
                    'state' => 'Telangana',
                    'pincode' => '506002',
                    'country' => 'IN',
                    'is_active' => true,
                    'fulfills_online' => false,
                    'allows_pickup' => false,
                    'priority' => 200,
                    'is_default' => true,
                ]);
            }

            $store = Location::defaultStore();
            if (!$store) {
                $store = Location::create([
                    'company_id' => $company->id,
                    'code' => 'ST-DEFAULT',
                    'name' => 'Shiva Jewellers Warangal',
                    'type' => Location::TYPE_STORE,
                    'phone' => '9966337448',
                    'address_line1' => config('services.delhivery.pickup_address', 'Warangal'),
                    'city' => 'Warangal',
                    'state' => 'Telangana',
                    'pincode' => config('services.delhivery.warehouse_pin', '506002'),
                    'country' => 'IN',
                    'is_active' => true,
                    'fulfills_online' => true,
                    'allows_pickup' => true,
                    'priority' => 1,
                    'is_default' => true,
                ]);
            }

            return $company->fresh(['locations']);
        });
    }
}
