<?php

namespace App\Livewire;

use App\Actions\CreateChannelAction;
use App\Enums\LogLevelEnum;
use App\Events\ActionPerformed;
use App\Models\ArticlePublication;
use App\Models\Language;
use App\Models\PlatformAccount;
use App\Models\PlatformChannel;
use App\Models\PlatformInstance;
use App\Models\RouteArticle;
use App\Services\Platform\CommunityDirectory;
use Exception;
use Illuminate\Contracts\View\View;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Validation\Rule;
use Livewire\Component;
use RuntimeException;

class Channels extends Component
{
    public ?int $managingChannelId = null;

    public bool $showCreateModal = false;

    public ?int $newCommunityId = null;

    public ?int $newPlatformInstanceId = null;

    /** @var array<int, array{id: int, name: string, title: string}> */
    public array $availableCommunities = [];

    public ?string $communityLoadError = null;

    public ?int $newLanguageId = null;

    public string $newDescription = '';

    public ?int $editingChannelId = null;

    public string $editDisplayName = '';

    public ?int $editLanguageId = null;

    public string $editDescription = '';

    public function toggle(int $channelId): void
    {
        $channel = PlatformChannel::findOrFail($channelId);
        $channel->is_active = ! $channel->is_active;
        $channel->save();
    }

    public function deleteChannel(int $channelId): void
    {
        $channel = PlatformChannel::find($channelId);

        if (! $channel instanceof PlatformChannel) {
            return;
        }

        $name = $channel->display_name;

        // Routes, keywords, route articles, publications, account links and synced posts
        // all cascade at the database level.
        $channel->delete();

        if ($this->managingChannelId === $channelId) {
            $this->managingChannelId = null;
        }

        if ($this->editingChannelId === $channelId) {
            $this->editingChannelId = null;
        }

        ActionPerformed::dispatch('Deleted platform channel', LogLevelEnum::WARNING, [
            'platform_channel_id' => $channelId,
            'display_name' => $name,
        ]);
    }

    public function openCreateModal(): void
    {
        $this->reset(['newCommunityId', 'newPlatformInstanceId', 'newLanguageId', 'newDescription', 'availableCommunities', 'communityLoadError']);
        $this->resetErrorBag();
        $this->showCreateModal = true;
    }

    public function updatedNewPlatformInstanceId(?int $value): void
    {
        $this->reset(['newCommunityId', 'availableCommunities', 'communityLoadError']);

        if (! $value) {
            return;
        }

        $instance = PlatformInstance::find($value);

        if (! $instance) {
            return;
        }

        try {
            $this->availableCommunities = app(CommunityDirectory::class)->forInstance($instance);
        } catch (Exception $e) {
            $this->communityLoadError = 'Could not reach this instance to list its communities: '.$e->getMessage();
        }
    }

    public function refreshCommunities(): void
    {
        $instance = $this->newPlatformInstanceId ? PlatformInstance::find($this->newPlatformInstanceId) : null;

        if (! $instance) {
            return;
        }

        app(CommunityDirectory::class)->forget($instance);
        $this->updatedNewPlatformInstanceId($this->newPlatformInstanceId);
    }

    public function closeCreateModal(): void
    {
        $this->showCreateModal = false;
    }

    public function createChannel(CreateChannelAction $action): void
    {
        $this->validate([
            'newCommunityId' => [
                'required',
                'integer',
                Rule::in(collect($this->availableCommunities)->pluck('id')->all()),
                Rule::unique('platform_channels', 'channel_id')
                    ->where('platform_instance_id', $this->newPlatformInstanceId),
            ],
            'newPlatformInstanceId' => 'required|integer|exists:platform_instances,id',
            'newLanguageId' => 'nullable|integer|exists:languages,id',
        ], [
            'newCommunityId.in' => 'Select a community from this instance.',
            'newCommunityId.unique' => 'A channel for this community already exists.',
        ]);

        $name = collect($this->availableCommunities)->firstWhere('id', $this->newCommunityId)['name'] ?? null;

        try {
            $action->execute(
                $name,
                $this->newCommunityId,
                $this->newPlatformInstanceId,
                $this->newLanguageId,
                // Blade textarea binds an empty string when blank; the action expects null for "no description".
                $this->newDescription !== '' ? $this->newDescription : null,
            );
        } catch (UniqueConstraintViolationException $e) {
            $this->addError('newCommunityId', 'A channel for this community already exists.');

            return;
        } catch (RuntimeException $e) {
            $this->addError('newPlatformInstanceId', $e->getMessage());

            return;
        }

        $this->closeCreateModal();
    }

    public function openEditModal(int $channelId): void
    {
        $channel = PlatformChannel::findOrFail($channelId);

        $this->resetErrorBag();
        $this->editingChannelId = $channelId;
        $this->editDisplayName = $channel->display_name;
        $this->editLanguageId = $channel->language_id;
        $this->editDescription = $channel->description ?? '';
    }

    public function closeEditModal(): void
    {
        $this->editingChannelId = null;
    }

    // The community pairing (name, channel_id, platform_instance_id) is deliberately immutable:
    // it is the channel's remote identity, unique per instance, and re-pointing it would change
    // the meaning of every route already attached.
    public function updateChannel(): void
    {
        if ($this->editingChannelId === null) {
            return;
        }

        $this->validate([
            'editDisplayName' => 'required|string|max:255',
            'editLanguageId' => 'nullable|integer|exists:languages,id',
        ]);

        PlatformChannel::findOrFail($this->editingChannelId)->update([
            'display_name' => $this->editDisplayName,
            'language_id' => $this->editLanguageId,
            'description' => $this->editDescription !== '' ? $this->editDescription : null,
        ]);

        $this->closeEditModal();
    }

    public function openAccountModal(int $channelId): void
    {
        $this->managingChannelId = $channelId;
    }

    public function closeAccountModal(): void
    {
        $this->managingChannelId = null;
    }

    public function attachAccount(int $accountId): void
    {
        if (! $this->managingChannelId) {
            return;
        }

        $channel = PlatformChannel::findOrFail($this->managingChannelId);

        if (! $channel->platformAccounts()->where('platform_account_id', $accountId)->exists()) {
            $channel->platformAccounts()->attach($accountId, [
                'is_active' => true,
                'priority' => 1,
                'created_at' => now(),
                'updated_at' => now(),
            ]);
        }
    }

    public function detachAccount(int $channelId, int $accountId): void
    {
        $channel = PlatformChannel::findOrFail($channelId);
        $channel->platformAccounts()->detach($accountId);
    }

    /**
     * Row counts per channel, so the delete confirmation can say what is about to go.
     *
     * @return array<int, array{articles: int, publications: int}>
     */
    private function deletionImpact(): array
    {
        /** @var array<int, int> $articles */
        $articles = RouteArticle::query()
            ->selectRaw('platform_channel_id, COUNT(*) as aggregate')
            ->groupBy('platform_channel_id')
            ->pluck('aggregate', 'platform_channel_id')
            ->all();

        /** @var array<int, int> $publications */
        $publications = ArticlePublication::query()
            ->selectRaw('platform_channel_id, COUNT(*) as aggregate')
            ->groupBy('platform_channel_id')
            ->pluck('aggregate', 'platform_channel_id')
            ->all();

        $impact = [];

        foreach (array_keys($articles + $publications) as $channelId) {
            $impact[$channelId] = [
                'articles' => (int) ($articles[$channelId] ?? 0),
                'publications' => (int) ($publications[$channelId] ?? 0),
            ];
        }

        return $impact;
    }

    public function render(): View
    {
        $channels = PlatformChannel::with(['platformInstance', 'platformAccounts'])->orderBy('name')->get();
        $allAccounts = PlatformAccount::where('is_active', true)->get();

        $managingChannel = $this->managingChannelId
            ? PlatformChannel::with('platformAccounts')->find($this->managingChannelId)
            : null;

        $availableAccounts = $managingChannel
            ? $allAccounts->filter(fn ($account) => ! $managingChannel->platformAccounts->contains('id', $account->id))
            : collect();

        return view('livewire.channels', [
            'channels' => $channels,
            'managingChannel' => $managingChannel,
            'editingChannel' => $this->editingChannelId !== null
                ? PlatformChannel::with('platformInstance')->find($this->editingChannelId)
                : null,
            'availableAccounts' => $availableAccounts,
            'deletionImpact' => $this->deletionImpact(),
            'platformInstances' => PlatformInstance::where('is_active', true)->orderBy('name')->get(),
            'languages' => Language::where('is_active', true)->orderBy('name')->get(),
        ])->layout('layouts.app');
    }
}
