<?php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Models\Organization;
use App\Models\Room;
use App\Models\User;
use App\Services\Integrations\WebhookDispatcher;
use App\Services\Meet\CalendarService;
use App\Services\Meet\InvitationService;
use App\Services\Meet\RoomService;
use App\Services\Meet\SessionService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;

class ServiceRoomController extends Controller
{
    private const SERVICE_TYPES = ['instant', 'scheduled', 'town_hall', 'webinar'];

    public function __construct(
        protected RoomService $rooms,
        protected SessionService $sessions,
        protected InvitationService $invitations,
        protected CalendarService $calendar,
        protected WebhookDispatcher $webhooks,
    ) {}

    public function store(Request $request): JsonResponse
    {
        $validated = $this->validateRoomPayload($request);

        $organization = $this->resolveOrganization($validated);
        abort_unless($organization->owner_ref === $validated['owner_ref'], 403);

        $entityId = (string) ($validated['source']['entity_id'] ?? '');
        if ($entityId !== '') {
            $existing = Room::query()
                ->forSource((string) $validated['source']['app'], $entityId, $validated['owner_ref'])
                ->first();

            if ($existing) {
                return $this->respondWithRoom(
                    $this->persistRoomUpdate($existing, $validated, $organization),
                );
            }
        }

        return $this->respondWithRoom(
            $this->persistRoomCreate($validated, $organization),
            201,
        );
    }

    public function lookup(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'source_app' => ['required', 'string'],
            'entity_id' => ['required', 'string'],
            'owner_ref' => ['nullable', 'string'],
        ]);

        $room = Room::query()
            ->forSource(
                $validated['source_app'],
                $validated['entity_id'],
                $validated['owner_ref'] ?? null,
            )
            ->first();

        if (! $room) {
            return response()->json(['error' => 'Room not found.'], 404);
        }

        return $this->respondWithRoom($room);
    }

    public function show(Room $room): JsonResponse
    {
        return $this->respondWithRoom($room->load('sessions'));
    }

    public function update(Request $request, Room $room): JsonResponse
    {
        $validated = $this->validateRoomPayload($request, partial: true);
        abort_unless($room->owner_ref === ($validated['owner_ref'] ?? $room->owner_ref), 403);

        $organization = isset($validated['organization_id'])
            ? Organization::findOrFail($validated['organization_id'])
            : $room->organization;

        return $this->respondWithRoom(
            $this->persistRoomUpdate($room, $validated, $organization),
        );
    }

    public function cancel(Request $request, Room $room): JsonResponse
    {
        $validated = $request->validate([
            'owner_ref' => ['required', 'string'],
            'host_user_ref' => ['nullable', 'string'],
        ]);

        abort_unless($room->owner_ref === $validated['owner_ref'], 403);

        $host = User::where('public_id', $validated['host_user_ref'] ?? $room->host_user_ref)->first();
        $this->rooms->cancel($room, $validated['owner_ref'], $host);

        return $this->respondWithRoom($room->fresh());
    }

    public function start(Request $request, Room $room): JsonResponse
    {
        $host = User::where('public_id', $request->input('host_user_ref', $room->host_user_ref))->firstOrFail();
        abort_unless($room->host_user_ref === $host->ownerRef(), 403);

        $session = $this->sessions->start($room, $host);

        return response()->json([
            'session' => $session,
            'room_url' => route('meet.room', $session),
            'join_url' => $room->joinUrl(),
        ]);
    }

    /** @return array<string, mixed> */
    private function validateRoomPayload(Request $request, bool $partial = false): array
    {
        $required = $partial ? 'sometimes' : 'required';

        return $request->validate([
            'owner_ref' => [$required, 'string'],
            'organization_id' => ['nullable', 'integer', 'exists:meet_organizations,id'],
            'host_user_ref' => [$partial ? 'sometimes' : 'required', 'string'],
            'title' => [$partial ? 'sometimes' : 'required', 'string', 'max:255'],
            'description' => ['nullable', 'string'],
            'type' => ['nullable', 'string', Rule::in(self::SERVICE_TYPES)],
            'scheduled_at' => ['nullable', 'date'],
            'duration_minutes' => ['nullable', 'integer', 'min:15', 'max:480'],
            'timezone' => ['nullable', 'timezone'],
            'passcode' => ['nullable', 'string', 'max:20'],
            'settings' => ['nullable', 'array'],
            'source' => [$partial ? 'sometimes' : 'required', 'array'],
            'source.app' => [$partial ? 'sometimes' : 'required', 'string'],
            'source.entity_type' => ['nullable', 'string'],
            'source.entity_id' => ['nullable', 'string'],
            'source.event_id' => ['nullable', 'string'],
            'invite_emails' => ['nullable', 'array'],
            'invite_emails.*' => ['email'],
        ]);
    }

    /** @param  array<string, mixed>  $validated */
    private function resolveOrganization(array $validated): Organization
    {
        return isset($validated['organization_id'])
            ? Organization::findOrFail($validated['organization_id'])
            : Organization::owned($validated['owner_ref'])->firstOrFail();
    }

    /** @param  array<string, mixed>  $validated */
    private function persistRoomCreate(array $validated, Organization $organization): Room
    {
        $host = User::where('public_id', $validated['host_user_ref'])->firstOrFail();
        $type = $this->resolveType($validated);

        $payload = array_merge($validated, [
            'type' => $type,
            'status' => 'scheduled',
            'settings' => $this->mergeSettingsForType($type, $validated['settings'] ?? []),
        ]);

        $room = $type === 'instant' && empty($validated['scheduled_at'])
            ? $this->rooms->createInstant($host, $organization, $payload)
            : $this->rooms->create($host, $organization, $payload);

        $this->afterRoomPersisted($room, $validated, $host);

        return $room->fresh();
    }

    /** @param  array<string, mixed>  $validated */
    private function persistRoomUpdate(Room $room, array $validated, Organization $organization): Room
    {
        $hostRef = $validated['host_user_ref'] ?? $room->host_user_ref;
        $host = User::where('public_id', $hostRef)->firstOrFail();

        $update = array_filter([
            'title' => $validated['title'] ?? null,
            'description' => array_key_exists('description', $validated) ? $validated['description'] : null,
            'scheduled_at' => $validated['scheduled_at'] ?? null,
            'duration_minutes' => $validated['duration_minutes'] ?? null,
            'timezone' => $validated['timezone'] ?? null,
            'passcode' => array_key_exists('passcode', $validated) ? $validated['passcode'] : null,
            'host_user_ref' => $hostRef,
            'source' => $validated['source'] ?? null,
        ], fn ($value) => $value !== null);

        if (isset($validated['settings'])) {
            $type = $this->resolveType(array_merge(['type' => $room->type], $validated));
            $update['settings'] = $this->mergeSettingsForType($type, $validated['settings']);
        }

        $room = $this->rooms->update($room, $update, $host);
        $this->afterRoomPersisted($room, $validated, $host);

        return $room;
    }

    /** @param  array<string, mixed>  $validated */
    private function afterRoomPersisted(Room $room, array $validated, User $host): void
    {
        if (! empty($validated['invite_emails'])) {
            $invites = collect($validated['invite_emails'])->map(fn ($email) => ['email' => $email])->all();
            $this->invitations->inviteToRoom($room, $invites, $host);
        }

        if ($room->scheduled_at) {
            $this->calendar->syncRoomCreated($room, $host);
        }
    }

    /** @param  array<string, mixed>  $validated */
    private function resolveType(array $validated): string
    {
        if (! empty($validated['type'])) {
            return (string) $validated['type'];
        }

        return empty($validated['scheduled_at']) ? 'instant' : 'scheduled';
    }

    /** @param  array<string, mixed>  $settings */
    private function mergeSettingsForType(string $type, array $settings): array
    {
        $defaults = match ($type) {
            'town_hall' => [
                'waiting_room' => true,
                'mute_on_join' => true,
                'green_room' => true,
                'stage_layout' => 'plenary',
                'stage_mode' => true,
                'panel_discussions' => false,
                'panels' => [],
                'presenter_refs' => [],
            ],
            'webinar' => [
                'waiting_room' => true,
                'mute_on_join' => true,
                'webinar_auto_approve' => false,
                'stage_layout' => 'plenary',
                'stage_mode' => true,
                'panel_discussions' => false,
                'panels' => [],
            ],
            default => [],
        };

        return array_merge(config('meet.default_settings', []), $defaults, $settings);
    }

    private function respondWithRoom(Room $room, int $status = 200): JsonResponse
    {
        return response()->json([
            'room' => $room->loadMissing('sessions'),
            'join_url' => $room->joinUrl(),
            'embed_url' => route('meet.join', $room),
        ], $status);
    }
}
