<?php

namespace App\Http\Controllers\Care;

use App\Http\Controllers\Care\Concerns\ScopesToAccount;
use App\Http\Controllers\Controller;
use App\Models\Branch;
use App\Models\CareDisplayScreen;
use App\Models\CareServiceQueue;
use App\Models\Device;
use App\Services\Care\CarePermissions;
use App\Services\Care\DeviceService;
use App\Services\Care\KioskService;
use App\Services\Care\OrganizationResolver;
use App\Services\Care\PlanService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class DeviceController extends Controller
{
    use ScopesToAccount;

    public function __construct(
        protected DeviceService $devices,
        protected PlanService $plans,
        protected KioskService $kiosk,
    ) {}

    public function index(Request $request): View
    {
        $this->authorizeAbility($request, 'devices.view');
        $organization = $this->organization($request);
        $branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));

        $query = Device::owned($this->ownerRef($request))
            ->where('organization_id', $organization->id)
            ->with('branch')
            ->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope))
            ->orderBy('name');

        $devices = (clone $query)->paginate(25);
        $all = (clone $query)->get();

        $heroStats = [
            'total' => $all->count(),
            'online' => $all->filter(fn (Device $d) => $d->isOnline())->count(),
            'kiosks' => $all->where('type', 'kiosk')->count(),
            'displays' => $all->where('type', 'display')->count(),
            'agent' => $all->where('connection_mode', Device::MODE_AGENT)->count(),
        ];

        return view('care.devices.index', [
            'organization' => $organization,
            'devices' => $devices,
            'deviceTypes' => config('care.device_types'),
            'connectionModes' => config('care.device_connection_modes'),
            'heroStats' => $heroStats,
            'canManage' => app(CarePermissions::class)
                ->can($this->member($request), 'devices.manage'),
            'hasAgentDevices' => $this->plans->hasPaidPlan($organization),
            'hasQueueDevices' => $this->plans->hasPaidPlan($organization),
        ]);
    }

    public function create(Request $request): View
    {
        $this->authorizeAbility($request, 'devices.manage');
        $organization = $this->organization($request);
        $owner = $this->ownerRef($request);
        $paid = $this->plans->hasPaidPlan($organization);

        return view('care.devices.create', [
            'organization' => $organization,
            'deviceTypes' => config('care.device_types'),
            'connectionModes' => config('care.device_connection_modes'),
            'branches' => $this->branches($request, $organization->id),
            'queues' => CareServiceQueue::owned($owner)
                ->where('organization_id', $organization->id)
                ->where('is_active', true)
                ->orderBy('name')
                ->get(),
            'layouts' => config('care.display_layouts'),
            'hasAgentDevices' => $paid,
            'hasQueueDevices' => $paid,
            'agentTypes' => config('care.device_agent_types'),
            'queueDeviceTypes' => config('care.queue_device_types', []),
            'defaultModes' => collect(config('care.device_types'))
                ->keys()
                ->mapWithKeys(fn ($type) => [$type => $this->devices->defaultConnectionMode($type)])
                ->all(),
            'prefillType' => $request->query('type'),
        ]);
    }

    public function store(Request $request): RedirectResponse
    {
        $this->authorizeAbility($request, 'devices.manage');
        $organization = $this->organization($request);
        $owner = $this->ownerRef($request);

        $validated = $this->validatedDevice($request, $organization);

        if (! $this->devices->canRegisterType($organization, $validated['type'], $this->plans)) {
            $message = $this->devices->isQueueDeviceType($validated['type'])
                ? 'Queue kiosks and displays require Care Pro or Enterprise.'
                : 'Agent-connected clinical devices require Care Pro or Enterprise. USB barcode/QR scanners work on Free.';

            return back()->withInput()->with('error', $message);
        }

        $mode = $validated['connection_mode']
            ?? $this->devices->defaultConnectionMode($validated['type']);

        $metadata = [];
        if ($validated['type'] === 'kiosk') {
            $allowed = CareServiceQueue::owned($owner)
                ->where('organization_id', $organization->id)
                ->pluck('id')
                ->all();
            $metadata = $this->kiosk->buildMetadata($validated, $allowed);
        }

        if ($validated['type'] === 'display') {
            if (empty($validated['branch_id'])) {
                return back()->withInput()->with('error', 'Queue displays require a branch.');
            }

            $queueIds = array_map('intval', $validated['queue_ids'] ?? []);
            $allowed = CareServiceQueue::owned($owner)
                ->where('organization_id', $organization->id)
                ->whereIn('id', $queueIds)
                ->pluck('id')
                ->all();

            $screen = CareDisplayScreen::create([
                'owner_ref' => $owner,
                'organization_id' => $organization->id,
                'branch_id' => $validated['branch_id'],
                'name' => $validated['name'],
                'layout' => $validated['layout'] ?? 'standard',
                'service_queue_ids' => array_values($allowed),
                'is_active' => true,
            ]);

            $metadata = [
                'display_screen_id' => $screen->id,
                'display_screen_uuid' => $screen->uuid,
                'service_queue_ids' => array_values($allowed),
            ];
        }

        $device = Device::create([
            'owner_ref' => $owner,
            'organization_id' => $organization->id,
            'branch_id' => $validated['branch_id'] ?? null,
            'name' => $validated['name'],
            'type' => $validated['type'],
            'connection_mode' => $mode,
            'status' => Device::STATUS_OFFLINE,
            'metadata' => $metadata,
        ]);

        $plainToken = null;
        $needsToken = $this->devices->isQueueDeviceType($validated['type'])
            || $mode === Device::MODE_AGENT
            || in_array($validated['type'], config('care.device_agent_types', []), true);

        if ($needsToken) {
            $plainToken = $this->devices->issueToken($device->fresh());
        }

        $redirect = redirect()->route('care.devices.edit', $device)
            ->with('success', 'Device registered.');

        if ($plainToken) {
            $redirect->with('device_token', $plainToken);
        }

        return $redirect;
    }

    public function edit(Request $request, Device $device): View
    {
        $this->authorizeAbility($request, 'devices.manage');
        $this->authorizeDevice($request, $device);
        $organization = $this->organization($request);
        $owner = $this->ownerRef($request);

        $displayScreen = null;
        $displayPublicUrl = null;
        if ($device->type === 'display' && ! empty($device->metadata['display_screen_id'])) {
            $displayScreen = CareDisplayScreen::query()->find($device->metadata['display_screen_id']);
            if ($displayScreen) {
                $displayPublicUrl = route('care.display.public', $displayScreen->access_token);
            }
        }

        $kioskUrl = null;
        if ($device->type === 'kiosk') {
            $token = $this->devices->publicAccessToken($device) ?? session('device_token');
            if ($token) {
                $kioskUrl = route('care.kiosk.device', $token);
            }
        }

        return view('care.devices.edit', [
            'device' => $device,
            'organization' => $organization,
            'deviceTypes' => config('care.device_types'),
            'connectionModes' => config('care.device_connection_modes'),
            'deviceStatuses' => config('care.device_statuses'),
            'branches' => $this->branches($request, $device->organization_id),
            'queues' => CareServiceQueue::owned($owner)
                ->where('organization_id', $organization->id)
                ->where('is_active', true)
                ->orderBy('name')
                ->get(),
            'layouts' => config('care.display_layouts'),
            'hasAgentDevices' => $this->plans->hasPaidPlan($organization),
            'hasQueueDevices' => $this->plans->hasPaidPlan($organization),
            'agentTypes' => config('care.device_agent_types'),
            'queueDeviceTypes' => config('care.queue_device_types', []),
            'connectionHint' => $this->devices->connectionHint($device->type, $device->connection_mode),
            'plainToken' => session('device_token'),
            'kioskUrl' => $kioskUrl,
            'displayScreen' => $displayScreen,
            'displayPublicUrl' => $displayPublicUrl,
            'kioskSettings' => $device->type === 'kiosk' ? $this->kiosk->settings($device) : null,
        ]);
    }

    public function update(Request $request, Device $device): RedirectResponse
    {
        $this->authorizeAbility($request, 'devices.manage');
        $this->authorizeDevice($request, $device);
        $organization = $this->organization($request);
        $owner = $this->ownerRef($request);

        $validated = $this->validatedDevice($request, $organization, updating: true);

        if (
            isset($validated['type'])
            && $validated['type'] !== $device->type
            && ! $this->devices->canRegisterType($organization, $validated['type'], $this->plans)
        ) {
            return back()->withInput()->with(
                'error',
                'That device type requires Care Pro or Enterprise.',
            );
        }

        $metadata = $device->metadata ?? [];
        if (($validated['type'] ?? $device->type) === 'kiosk') {
            $allowed = CareServiceQueue::owned($owner)
                ->where('organization_id', $organization->id)
                ->pluck('id')
                ->all();
            $metadata = array_merge(
                $metadata,
                $this->kiosk->buildMetadata($validated, $allowed),
            );
            if ($access = $this->devices->publicAccessToken($device)) {
                $metadata['access_token'] = $access;
            }
        }

        if (($validated['type'] ?? $device->type) === 'display') {
            $queueIds = array_map('intval', $validated['queue_ids'] ?? ($metadata['service_queue_ids'] ?? []));
            $allowed = CareServiceQueue::owned($owner)
                ->where('organization_id', $organization->id)
                ->whereIn('id', $queueIds)
                ->pluck('id')
                ->all();
            $metadata['service_queue_ids'] = array_values($allowed);

            if (! empty($metadata['display_screen_id'])) {
                CareDisplayScreen::query()->where('id', $metadata['display_screen_id'])->update([
                    'name' => $validated['name'],
                    'branch_id' => $validated['branch_id'] ?? null,
                    'layout' => $validated['layout'] ?? 'standard',
                    'service_queue_ids' => array_values($allowed),
                ]);
            }
        }

        $device->update([
            'name' => $validated['name'],
            'type' => $validated['type'],
            'branch_id' => $validated['branch_id'] ?? null,
            'connection_mode' => $validated['connection_mode']
                ?? $device->connection_mode
                ?? $this->devices->defaultConnectionMode($validated['type']),
            'status' => $validated['status'] ?? $device->status,
            'metadata' => $metadata,
        ]);

        return redirect()->route('care.devices.index')->with('success', 'Device updated.');
    }

    public function destroy(Request $request, Device $device): RedirectResponse
    {
        $this->authorizeAbility($request, 'devices.manage');
        $this->authorizeDevice($request, $device);
        $device->delete();

        return redirect()->route('care.devices.index')->with('success', 'Device removed.');
    }

    public function regenerateToken(Request $request, Device $device): RedirectResponse
    {
        $this->authorizeAbility($request, 'devices.manage');
        $this->authorizeDevice($request, $device);

        $plain = $this->devices->issueToken($device);

        return back()
            ->with('success', 'Device token regenerated. Copy it now — it will not be shown again.')
            ->with('device_token', $plain);
    }

    public function revokeToken(Request $request, Device $device): RedirectResponse
    {
        $this->authorizeAbility($request, 'devices.manage');
        $this->authorizeDevice($request, $device);
        $this->devices->revokeToken($device);

        return back()->with('success', 'Device token revoked.');
    }

    protected function authorizeDevice(Request $request, Device $device): void
    {
        $this->authorizeOwner($request, $device);
        abort_unless($device->organization_id === $this->organization($request)->id, 404);

        $branchId = app(OrganizationResolver::class)->branchScope($this->member($request));
        if ($branchId !== null && $device->branch_id !== null && $device->branch_id !== $branchId) {
            abort(404);
        }
    }

    /**
     * @return array<string, mixed>
     */
    protected function validatedDevice(Request $request, $organization, bool $updating = false): array
    {
        $rules = [
            'name' => ['required', 'string', 'max:255'],
            'type' => ['required', 'string', 'in:'.implode(',', array_keys(config('care.device_types')))],
            'branch_id' => ['nullable', 'integer', 'exists:care_branches,id'],
            'connection_mode' => ['nullable', 'string', 'in:'.implode(',', array_keys(config('care.device_connection_modes')))],
            'queue_ids' => ['nullable', 'array'],
            'queue_ids.*' => ['integer', 'exists:care_service_queues,id'],
            'collect_name' => ['nullable', 'boolean'],
            'collect_phone' => ['nullable', 'boolean'],
            'reset_seconds' => ['nullable', 'integer', 'min:5', 'max:120'],
            'welcome_message' => ['nullable', 'string', 'max:255'],
            'layout' => ['nullable', 'string', 'in:'.implode(',', array_keys(config('care.display_layouts', ['standard' => 'Standard'])))],
        ];

        if ($updating) {
            $rules['status'] = ['nullable', 'string', 'in:'.implode(',', array_keys(config('care.device_statuses')))];
        }

        $validated = $request->validate($rules);

        if (! empty($validated['branch_id'])) {
            $ok = Branch::owned($this->ownerRef($request))
                ->where('organization_id', $organization->id)
                ->where('id', $validated['branch_id'])
                ->exists();
            abort_unless($ok, 422);
        }

        $validated['collect_name'] = $request->boolean('collect_name');
        $validated['collect_phone'] = $request->boolean('collect_phone');

        return $validated;
    }

    /** @return \Illuminate\Database\Eloquent\Collection<int, Branch> */
    protected function branches(Request $request, int $organizationId)
    {
        $branchScope = app(OrganizationResolver::class)->branchScope($this->member($request));

        return Branch::owned($this->ownerRef($request))
            ->where('organization_id', $organizationId)
            ->where('is_active', true)
            ->when($branchScope, fn ($q) => $q->where('id', $branchScope))
            ->orderBy('name')
            ->get();
    }
}
