<?php

namespace App\Http\Livewire\Wiki;

use App\Models\Artigo;
use App\Models\ArtigoAnexo;
use App\Models\ArtigoVisualizacao;
use App\Models\ArtigoVoto;
use App\Models\Categoria;
use App\Models\Comentario;
use App\Models\Departamento;
use App\Models\Favorito;
use App\Models\Subcategoria;
use App\Models\WikiBusca;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\WithPagination;

class WikiIndex extends Component
{
    use AuthorizesRequests;
    use WithFileUploads;
    use WithPagination;

    public ?int $departamentoId = null;
    public ?int $subcategoriaId = null;
    public string $busca = '';
    public string $ordenacao = 'recentes';
    public bool $modalAberto = false;
    public ?int $artigoSelecionadoId = null;
    public ?int $artigoEditandoId = null;
    public string $titulo = '';
    public string $conteudo = '';
    public string $status = 'rascunho';
    public bool $restrito = false;
    public ?int $subcategoria_id = null;
    public string $comentario = '';
    public string $resumoAlteracoes = '';
    public $novosAnexos = [];

    private ?string $ultimaBuscaRegistrada = null;

    protected $paginationTheme = 'tailwind';

    protected $queryString = [
        'busca' => ['except' => ''],
        'ordenacao' => ['except' => 'recentes'],
    ];

    protected $listeners = [
        'subcategoriasAtualizadas' => '$refresh',
    ];

    public function mount($departamento = null, $subcategoria = null): void
    {
        $departamentoSelecionado = $this->resolverDepartamento($departamento);
        $subcategoriaSelecionada = $this->resolverSubcategoria($subcategoria, $departamentoSelecionado?->id);

        $this->departamentoId = $departamentoSelecionado?->id;
        $this->subcategoriaId = $subcategoriaSelecionada?->id;

        if ($subcategoriaSelecionada) {
            $this->subcategoria_id = $subcategoriaSelecionada->id;
        }
    }

    public function updatedBusca(string $valor): void
    {
        $this->resetPage();
        $this->registrarBusca($valor);
    }

    public function updatedOrdenacao(): void
    {
        $this->resetPage();
    }

    public function abrirCriacao(): void
    {
        $this->authorize('wiki.criar');
        abort_unless($this->subcategoriaId, 404);

        $this->resetFormulario();
        $this->subcategoria_id = $this->subcategoriaId;
        $this->modalAberto = true;
    }

    public function editar(int $artigoId): void
    {
        $this->authorize('wiki.editar');

        $artigo = Artigo::with('departamento', 'subcategoria')->findOrFail($artigoId);
        abort_unless($this->usuarioPodeEditar($artigo), 403);

        $this->artigoEditandoId = $artigo->id;
        $this->titulo = $artigo->titulo;
        $this->conteudo = $artigo->conteudo;
        $this->status = $artigo->status;
        $this->restrito = $artigo->restrito;
        $this->subcategoria_id = $artigo->subcategoria_id ?: $this->subcategoriaId;
        $this->resumoAlteracoes = '';
        $this->novosAnexos = [];
        $this->modalAberto = true;
    }

    public function salvar(): void
    {
        $this->authorize($this->artigoEditandoId ? 'wiki.editar' : 'wiki.criar');

        $dados = $this->validate([
            'titulo' => ['required', 'string', 'max:180'],
            'conteudo' => ['required', 'string', 'min:10'],
            'status' => ['required', Rule::in(['rascunho', 'publicado'])],
            'restrito' => ['boolean'],
            'subcategoria_id' => ['required', 'exists:subcategorias,id'],
            'resumoAlteracoes' => ['nullable', 'string', 'max:180'],
            'novosAnexos.*' => ['nullable', 'file', 'max:102400'],
        ]);

        $subcategoria = Subcategoria::with('departamento')->findOrFail($dados['subcategoria_id']);
        abort_unless($subcategoria->ativo && $subcategoria->departamento->ativo, 422);

        $artigo = $this->artigoEditandoId
            ? Artigo::findOrFail($this->artigoEditandoId)
            : new Artigo(['user_id' => Auth::id()]);

        abort_unless(! $artigo->exists || $this->usuarioPodeEditar($artigo), 403);

        $artigo->fill([
            'departamento_id' => $subcategoria->departamento_id,
            'subcategoria_id' => $subcategoria->id,
            'categoria_id' => $this->categoriaLegadaId($subcategoria->departamento_id),
            'titulo' => $dados['titulo'],
            'slug' => $this->gerarSlugUnico($dados['titulo'], $artigo->id),
            'conteudo' => $this->sanitizarConteudo($dados['conteudo']),
            'status' => $dados['status'],
            'restrito' => $dados['restrito'],
        ]);

        if (! $artigo->exists) {
            $artigo->user_id = Auth::id();
        }

        $artigo->save();
        $this->salvarAnexos($artigo);
        $this->registrarVersao($artigo, $dados['resumoAlteracoes'] ?: 'Atualizacao do artigo.');

        $this->departamentoId = $subcategoria->departamento_id;
        $this->subcategoriaId = $subcategoria->id;
        $this->artigoSelecionadoId = $artigo->id;
        $this->modalAberto = false;
        $this->resetFormulario(false);
        $this->dispatchBrowserEvent('notify', ['message' => 'Artigo salvo com sucesso.']);
    }

    public function selecionar(int $artigoId): void
    {
        $artigo = $this->consultaVisivel()->findOrFail($artigoId);
        $this->artigoSelecionadoId = $artigo->id;
        $this->registrarVisualizacao($artigo);
    }

    public function favoritar(int $artigoId): void
    {
        $artigo = $this->consultaVisivel()->findOrFail($artigoId);

        $favorito = Favorito::where('user_id', Auth::id())
            ->where('favoritavel_type', Artigo::class)
            ->where('favoritavel_id', $artigo->id)
            ->first();

        $favorito
            ? $favorito->delete()
            : Favorito::create([
                'user_id' => Auth::id(),
                'favoritavel_type' => Artigo::class,
                'favoritavel_id' => $artigo->id,
            ]);
    }

    public function votar(int $artigoId, int $valor): void
    {
        abort_unless(in_array($valor, [1, -1], true), 422);

        $artigo = $this->consultaVisivel(false)->findOrFail($artigoId);
        $votoAtual = ArtigoVoto::where('artigo_id', $artigo->id)
            ->where('user_id', Auth::id())
            ->first();

        if ($votoAtual && (int) $votoAtual->valor === $valor) {
            $votoAtual->delete();
            $this->dispatchBrowserEvent('notify', ['message' => 'Voto removido.']);
            return;
        }

        ArtigoVoto::updateOrCreate(
            [
                'artigo_id' => $artigo->id,
                'user_id' => Auth::id(),
            ],
            ['valor' => $valor]
        );

        $this->dispatchBrowserEvent('notify', ['message' => 'Obrigado pelo feedback.']);
    }

    public function comentar(): void
    {
        $this->authorize('wiki.comentar');
        $artigo = $this->artigoSelecionado();

        if (! $artigo) {
            return;
        }

        $dados = $this->validate([
            'comentario' => ['required', 'string', 'min:2', 'max:1000'],
        ]);

        Comentario::create([
            'user_id' => Auth::id(),
            'comentavel_type' => Artigo::class,
            'comentavel_id' => $artigo->id,
            'conteudo' => e($dados['comentario']),
        ]);

        $this->comentario = '';
        $this->dispatchBrowserEvent('notify', ['message' => 'Comentario publicado.']);
    }

    public function artigoSelecionado(): ?Artigo
    {
        if (! $this->artigoSelecionadoId) {
            return null;
        }

        return $this->consultaVisivel(false)
            ->with([
                'autor',
                'departamento',
                'subcategoria',
                'categoria',
                'anexos',
                'versoes.usuario',
                'comentarios.usuario',
                'favoritos',
                'votos',
            ])
            ->withCount('visualizacoes')
            ->withSum('votos', 'valor')
            ->find($this->artigoSelecionadoId);
    }

    public function usuarioPodeEditar(Artigo $artigo): bool
    {
        $usuario = Auth::user();

        return $usuario->hasRole('Administrador Master')
            || ((int) $usuario->departamento_id === (int) $artigo->departamento_id);
    }

    public function podeGerenciarSubcategorias(): bool
    {
        return Auth::user()?->hasRole('Administrador Master')
            || Auth::user()?->can('departamentos.gerenciar');
    }

    public function render()
    {
        $artigosQuery = $this->consultaVisivel()
            ->with(['departamento', 'subcategoria', 'categoria', 'autor', 'favoritos'])
            ->withCount('visualizacoes')
            ->withSum('votos', 'valor');

        match ($this->ordenacao) {
            'populares' => $artigosQuery->orderByDesc('visualizacoes_count')->latest(),
            'uteis' => $artigosQuery->orderByDesc('votos_sum_valor')->latest(),
            default => $artigosQuery->latest(),
        };

        return view('livewire.wiki.wiki-index', [
            'departamentos' => $this->departamentos(),
            'departamentoSelecionado' => $this->departamentoSelecionado(),
            'subcategorias' => $this->subcategorias(),
            'subcategoriaSelecionada' => $this->subcategoriaSelecionada(),
            'subcategoriasFormulario' => $this->subcategoriasFormulario(),
            'artigos' => $this->subcategoriaId ? $artigosQuery->paginate(9) : null,
            'artigoSelecionado' => $this->artigoSelecionado(),
            'maisAcessados' => $this->maisAcessados(),
            'termosBuscados' => $this->termosBuscados(),
            'artigosRelacionados' => $this->artigosRelacionados(),
            'podeGerenciarDepartamentos' => $this->podeGerenciarSubcategorias(),
        ])->layout('layouts.module');
    }

    private function departamentos()
    {
        return Departamento::where('ativo', true)
            ->withCount(['subcategorias' => fn ($query) => $query->where('ativo', true), 'artigos'])
            ->orderBy('nome')
            ->get();
    }

    private function departamentoSelecionado(): ?Departamento
    {
        if (! $this->departamentoId) {
            return null;
        }

        return Departamento::where('ativo', true)->findOrFail($this->departamentoId);
    }

    private function subcategorias()
    {
        if (! $this->departamentoId) {
            return collect();
        }

        return Subcategoria::where('departamento_id', $this->departamentoId)
            ->where('ativo', true)
            ->withCount(['artigos' => fn ($query) => $this->restringirArtigosVisiveis($query)])
            ->orderBy('nome')
            ->get();
    }

    private function subcategoriaSelecionada(): ?Subcategoria
    {
        if (! $this->subcategoriaId) {
            return null;
        }

        return Subcategoria::where('ativo', true)->findOrFail($this->subcategoriaId);
    }

    private function subcategoriasFormulario()
    {
        $departamentoId = $this->departamentoId ?: $this->departamentoSelecionado()?->id;

        return Subcategoria::where('departamento_id', $departamentoId)
            ->where('ativo', true)
            ->orderBy('nome')
            ->get();
    }

    private function consultaVisivel(bool $comFiltros = true): Builder
    {
        $usuario = Auth::user();

        return Artigo::query()
            ->where(function ($query) use ($usuario) {
                $query->where('restrito', false)
                    ->orWhere('departamento_id', $usuario->departamento_id);

                if ($usuario->hasRole('Administrador Master')) {
                    $query->orWhere('restrito', true);
                }
            })
            ->when(! $usuario->can('wiki.editar'), fn ($query) => $query->where('status', 'publicado'))
            ->when($this->departamentoId, fn ($query) => $query->where('departamento_id', $this->departamentoId))
            ->when($this->subcategoriaId, fn ($query) => $query->where('subcategoria_id', $this->subcategoriaId))
            ->when($comFiltros && $this->busca, function ($query) {
                $termo = '%' . $this->busca . '%';
                $query->where(function ($subQuery) use ($termo) {
                    $subQuery->where('titulo', 'like', $termo)
                        ->orWhere('conteudo', 'like', $termo);
                });
            });
    }

    private function restringirArtigosVisiveis(Builder $query): Builder
    {
        $usuario = Auth::user();

        return $query
            ->where(function ($subQuery) use ($usuario) {
                $subQuery->where('restrito', false)
                    ->orWhere('departamento_id', $usuario->departamento_id);

                if ($usuario->hasRole('Administrador Master')) {
                    $subQuery->orWhere('restrito', true);
                }
            })
            ->when(! $usuario->can('wiki.editar'), fn ($subQuery) => $subQuery->where('status', 'publicado'));
    }

    private function maisAcessados()
    {
        return $this->consultaVisivel(false)
            ->with('subcategoria')
            ->withCount('visualizacoes')
            ->orderByDesc('visualizacoes_count')
            ->latest()
            ->limit(5)
            ->get();
    }

    private function termosBuscados()
    {
        return WikiBusca::query()
            ->selectRaw('termo, COUNT(*) as total, MAX(created_at) as ultima_busca')
            ->groupBy('termo')
            ->orderByDesc('total')
            ->limit(5)
            ->get();
    }

    private function artigosRelacionados()
    {
        if (! $this->artigoSelecionadoId) {
            return collect();
        }

        $artigo = Artigo::find($this->artigoSelecionadoId);

        if (! $artigo || ! $artigo->subcategoria_id) {
            return collect();
        }

        return $this->consultaVisivel(false)
            ->where('id', '!=', $artigo->id)
            ->where('subcategoria_id', $artigo->subcategoria_id)
            ->withCount('visualizacoes')
            ->latest()
            ->limit(4)
            ->get();
    }

    private function registrarVisualizacao(Artigo $artigo): void
    {
        $sessionKey = "wiki.artigo.{$artigo->id}.visualizado";

        if (session()->has($sessionKey)) {
            return;
        }

        ArtigoVisualizacao::create([
            'artigo_id' => $artigo->id,
            'user_id' => Auth::id(),
            'ip' => request()->ip(),
            'user_agent' => Str::limit((string) request()->userAgent(), 500, ''),
            'created_at' => now(),
        ]);

        session()->put($sessionKey, now()->timestamp);
    }

    private function registrarBusca(string $valor): void
    {
        $termo = trim($valor);

        if (Str::length($termo) < 2 || $termo === $this->ultimaBuscaRegistrada) {
            return;
        }

        $sessionKey = 'wiki.busca.' . md5(Str::lower($termo));

        if (session()->has($sessionKey)) {
            return;
        }

        WikiBusca::create([
            'user_id' => Auth::id(),
            'termo' => Str::limit($termo, 160, ''),
            'resultados' => $this->consultaVisivel()->count(),
            'ip' => request()->ip(),
            'created_at' => now(),
        ]);

        $this->ultimaBuscaRegistrada = $termo;
        session()->put($sessionKey, now()->timestamp);
    }

    private function salvarAnexos(Artigo $artigo): void
    {
        foreach ((array) $this->novosAnexos as $arquivo) {
            $caminho = $arquivo->store("wiki/artigos/{$artigo->id}", 'public');
            $mime = $arquivo->getMimeType();
            $tipo = match (true) {
                str_starts_with((string) $mime, 'image/') => 'imagem',
                str_starts_with((string) $mime, 'video/') => 'video',
                default => 'anexo',
            };

            ArtigoAnexo::create([
                'artigo_id' => $artigo->id,
                'user_id' => Auth::id(),
                'nome_original' => $arquivo->getClientOriginalName(),
                'caminho' => $caminho,
                'tipo' => $tipo,
                'mime' => $mime,
                'tamanho' => $arquivo->getSize(),
            ]);
        }
    }

    private function registrarVersao(Artigo $artigo, string $resumo): void
    {
        $proximaVersao = ((int) $artigo->versoes()->max('versao')) + 1;

        $artigo->versoes()->create([
            'user_id' => Auth::id(),
            'versao' => $proximaVersao,
            'titulo' => $artigo->titulo,
            'conteudo' => $artigo->conteudo,
            'resumo_alteracoes' => $resumo,
        ]);
    }

    private function resetFormulario(bool $selecionarSubcategoria = true): void
    {
        $this->reset(['artigoEditandoId', 'titulo', 'conteudo', 'comentario', 'resumoAlteracoes', 'novosAnexos']);
        $this->status = 'rascunho';
        $this->restrito = false;

        if ($selecionarSubcategoria) {
            $this->subcategoria_id = $this->subcategoriaId;
        }
    }

    private function gerarSlugUnico(string $titulo, ?int $ignorarId = null): string
    {
        $base = Str::slug($titulo) ?: Str::random(8);
        $slug = $base;
        $contador = 2;

        while (Artigo::where('slug', $slug)->when($ignorarId, fn ($query) => $query->where('id', '!=', $ignorarId))->exists()) {
            $slug = "{$base}-{$contador}";
            $contador++;
        }

        return $slug;
    }

    private function categoriaLegadaId(int $departamentoId): ?int
    {
        return Categoria::where('departamento_id', $departamentoId)->value('id');
    }

    private function sanitizarConteudo(string $conteudo): string
    {
        $permitidas = '<p><br><strong><b><em><i><u><s><ol><ul><li><blockquote><pre><code><h1><h2><h3><a><span><img>';
        $html = strip_tags($conteudo, $permitidas);
        $html = preg_replace('/\s+on\w+="[^"]*"/i', '', $html);
        $html = preg_replace("/\s+on\w+='[^']*'/i", '', $html);
        $html = preg_replace('/javascript:/i', '', $html);
        $html = preg_replace('/<img([^>]+)src=["\']data:[^"\']+["\']([^>]*)>/i', '', $html);

        return $html ?? '';
    }

    private function resolverDepartamento($departamento): ?Departamento
    {
        if (! $departamento) {
            return null;
        }

        $query = Departamento::where('ativo', true);

        return is_numeric($departamento)
            ? $query->findOrFail((int) $departamento)
            : $query->where('slug', $departamento)->firstOrFail();
    }

    private function resolverSubcategoria($subcategoria, ?int $departamentoId): ?Subcategoria
    {
        if (! $subcategoria) {
            return null;
        }

        abort_unless($departamentoId, 404);

        $query = Subcategoria::where('ativo', true)
            ->where('departamento_id', $departamentoId);

        return is_numeric($subcategoria)
            ? $query->findOrFail((int) $subcategoria)
            : $query->where('slug', $subcategoria)->firstOrFail();
    }
}
