import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../core/api/api_client.dart';
import '../../../core/cache/cache_local.dart';
import '../../auth/presentation/sessao_controller.dart';
import '../../boloes/data/boloes_repository.dart';
import '../../boloes/data/token_de_convite.dart';
import '../../boloes/data/convite_pendente.dart';
import '../../boloes/presentation/bolao_page.dart';
import '../../boloes/presentation/criar_bolao_page.dart';
import '../../boloes/presentation/rodada_page.dart';
import 'historico_page.dart';
import '../../../core/tema/tema.dart';

/// Tela inicial: os bolões do participante, criação e entrada por convite.
///
/// Quem joga em dois bolões precisa reconhecer o seu na lista sem abrir cada
/// um — daí nome, rodadas e quantidade de gente na própria linha.
class HomePage extends ConsumerStatefulWidget {
  const HomePage({super.key});

  @override
  ConsumerState<HomePage> createState() => _HomePageState();
}

class _HomePageState extends ConsumerState<HomePage> {
  List<BolaoResumo>? _boloes;
  String? _erro;
  bool _carregando = true;

  /// Idade do dado quando ele veio do cache. Nulo = veio da rede agora.
  Duration? _idadeDoCache;

  /// Arquivados começam recolhidos: são histórico, não o que a pessoa abre todo
  /// dia — mas continuam alcançáveis, com o motivo dito.
  bool _mostrarArquivados = false;

  @override
  void initState() {
    super.initState();
    _carregar();
    // Convite que chegou por deep link, possivelmente antes do login: usa agora
    // que há sessão. Sem isto o token ficaria guardado sem nunca ser aplicado.
    Future.microtask(_usarConvitePendente);
  }

  Future<void> _usarConvitePendente() async {
    final token = ref.read(convitePendenteProvider.notifier).consumir();
    if (token == null || !mounted) return;
    await _entrarCom(token);
  }

  Future<void> _carregar() async {
    setState(() => _erro = null);
    final cache = ref.read(cacheLocalProvider);
    try {
      final lista = await ref.read(boloesRepositoryProvider).meus();
      if (!mounted) return;
      setState(() {
        _boloes = lista;
        _idadeDoCache = null;
      });
      await cache.gravar(
        _chaveDosBoloes,
        lista.map((b) => b.paraJson()).toList(),
      );
    } on ApiError catch (e) {
      if (!mounted) return;
      // Sem rede, o cache é a diferença entre um app inútil e um app que ainda
      // deixa abrir o bolão e ver o palpite guardado. Fecha o limite que estava
      // registrado em `PP-4`.
      final guardado = await cache.ler(_chaveDosBoloes);
      if (!mounted) return;
      if (guardado != null && guardado.valor is List) {
        setState(() {
          _boloes = (guardado.valor as List<dynamic>)
              .cast<Map<String, dynamic>>()
              .map(BolaoResumo.doJson)
              .toList();
          _idadeDoCache = guardado.idade;
          _erro = null;
        });
      } else {
        setState(() => _erro = e.mensagem);
      }
    } finally {
      if (mounted) setState(() => _carregando = false);
    }
  }

  static const _chaveDosBoloes = 'meus-boloes';

  Future<void> _abrir(BolaoResumo b) async {
    await Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (_) => BolaoPage(
          poolId: b.id,
          nome: b.name,
          matchday: b.matchdayFrom,
          souAdmin: b.isAdmin,
          // Arquivado é somente leitura, como encerrado: o ranking final e o
          // extrato continuam acessíveis, o palpite não (CV-6 FE-3).
          encerrado: b.encerrado || b.arquivado,
          arquivado: b.arquivado,
          rodadaFinal: b.matchdayTo,
        ),
      ),
    );
    // Voltou de um bolão: pode ter entrado gente ou encerrado.
    await _carregar();
  }

  Future<void> _criar() async {
    await Navigator.of(
      context,
    ).push(MaterialPageRoute<void>(builder: (_) => const CriarBolaoPage()));
    await _carregar();
  }

  /// Entrada por convite digitado. Continua existindo ao lado do deep link:
  /// código colado é o caminho de quem recebeu o convite em outro aparelho, ou
  /// por um canal que não vira link clicável.
  Future<void> _entrarPorConvite() async {
    // O controller é do próprio diálogo (`_DialogoDeConvite`), não desta tela.
    // Criá-lo aqui e descartá-lo assim que `showDialog` retorna usava um
    // controller **já descartado** durante a animação de saída do diálogo — o
    // `TextField` continua sendo construído por alguns frames depois do pop.
    final token = await showDialog<String>(
      context: context,
      builder: (_) => const _DialogoDeConvite(),
    );
    if (token == null || token.isEmpty || !mounted) return;
    await _entrarCom(token);
  }

  /// Entra no bolão e **vai direto para a tela de palpite**.
  ///
  /// Parar na lista e pedir mais um toque é o passo extra que a story quer
  /// eliminar: quem acabou de aceitar um convite quer palpitar, não navegar.
  Future<void> _entrarCom(String colado) async {
    // Aceita o **link** colado, não só o código: é o que a pessoa recebeu.
    final token = tokenDeConvite(colado);
    if (token.isEmpty) return;
    try {
      final r = await ref
          .read(boloesRepositoryProvider)
          .entrarPorConvite(token);
      if (!mounted) return;
      // Convite reusado por quem já está dentro não é erro: entra no bolão.
      _avisar(
        r.jaParticipava
            ? 'Você já participa de ${r.poolName}'
            : 'Você entrou em ${r.poolName}',
        sucesso: true,
      );

      await _carregar();
      if (!mounted) return;

      final bolao = (_boloes ?? const <BolaoResumo>[])
          .where((b) => b.id == r.poolId)
          .firstOrNull;
      if (bolao == null) return;

      await Navigator.of(context).push(
        MaterialPageRoute<void>(
          builder: (_) => BolaoPage(
            poolId: bolao.id,
            nome: bolao.name,
            matchday: bolao.matchdayFrom,
            souAdmin: bolao.isAdmin,
            encerrado: bolao.encerrado,
            rodadaFinal: bolao.matchdayTo,
            // Só na entrada: quem já participava não precisa rever as regras.
            mostrarBoasVindas: !r.jaParticipava,
          ),
        ),
      );
      await _carregar();
    } on ApiError catch (e) {
      // Convite expirado/revogado tem texto próprio vindo do servidor —
      // "erro" genérico aqui faz o convidado achar que o app quebrou.
      if (mounted) _avisar(e.mensagem);
    }
  }

  void _avisar(String texto, {bool sucesso = false}) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(texto),
        backgroundColor: sucesso ? CoresDoApp.sucesso : null,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final sessao = ref.watch(sessaoProvider);
    final nome = sessao is Autenticado ? sessao.conta.displayName : '';

    return Scaffold(
      appBar: AppBar(
        title: Text(nome.isEmpty ? 'Bolão' : 'Olá, $nome'),
        actions: [
          IconButton(
            tooltip: 'Histórico e títulos',
            icon: const Icon(Icons.emoji_events_outlined),
            onPressed: () => Navigator.of(context).push(
              MaterialPageRoute<void>(builder: (_) => const HistoricoPage()),
            ),
          ),
          IconButton(
            tooltip: 'Entrar por convite',
            icon: const Icon(Icons.login),
            onPressed: _entrarPorConvite,
          ),
          IconButton(
            tooltip: 'Sair',
            icon: const Icon(Icons.logout),
            onPressed: () => ref.read(sessaoProvider.notifier).sair(),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: _criar,
        icon: const Icon(Icons.add),
        label: const Text('Criar bolão'),
      ),
      body: RefreshIndicator(onRefresh: _carregar, child: _corpo()),
    );
  }

  Widget _corpo() {
    if (_carregando) return const Center(child: CircularProgressIndicator());

    if (_erro != null && _boloes == null) {
      return _Centralizado(
        icone: Icons.cloud_off,
        titulo: 'Não foi possível carregar seus bolões',
        detalhe: _erro!,
        acao: FilledButton(
          onPressed: _carregar,
          child: const Text('Tentar de novo'),
        ),
      );
    }

    final boloes = _boloes ?? const <BolaoResumo>[];
    if (boloes.isEmpty) {
      // Primeira vez no app: os dois caminhos reais aparecem nomeados, porque
      // metade de quem chega foi convidado e não quer criar nada.
      return _Centralizado(
        icone: Icons.emoji_events_outlined,
        titulo: 'Você ainda não está em nenhum bolão',
        detalhe: 'Crie o seu ou entre com o código que te mandaram.',
        acao: OutlinedButton.icon(
          onPressed: _entrarPorConvite,
          icon: const Icon(Icons.login),
          label: const Text('Entrar por convite'),
        ),
      );
    }

    final ativos = boloes.where((b) => !b.arquivado).toList();
    final arquivados = boloes.where((b) => b.arquivado).toList();
    final visiveis = [...ativos, if (_mostrarArquivados) ...arquivados];

    return ListView.separated(
      padding: const EdgeInsets.only(top: 8, bottom: 96),
      // +1 para a faixa de idade do cache, +1 para o botão dos arquivados.
      itemCount:
          visiveis.length +
          (_idadeDoCache != null ? 1 : 0) +
          (arquivados.isEmpty ? 0 : 1),
      separatorBuilder: (_, _) => const Divider(height: 1),
      itemBuilder: (_, indice) {
        var i = indice;

        if (_idadeDoCache != null) {
          if (i == 0) {
            return Container(
              color: Theme.of(context).colorScheme.secondaryContainer,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
              child: Text(
                'Sem conexão — mostrando a lista guardada no aparelho, '
                '${descreverIdade(_idadeDoCache!)}.',
                style: const TextStyle(fontSize: 12),
              ),
            );
          }
          i -= 1;
        }

        if (arquivados.isNotEmpty && i == visiveis.length) {
          return TextButton.icon(
            onPressed: () =>
                setState(() => _mostrarArquivados = !_mostrarArquivados),
            icon: Icon(
              _mostrarArquivados ? Icons.expand_less : Icons.expand_more,
            ),
            label: Text(
              _mostrarArquivados
                  ? 'Esconder arquivados'
                  : 'Ver ${arquivados.length} bolão(ões) arquivado(s)',
            ),
          );
        }

        final b = visiveis[i];
        return ListTile(
          leading: Icon(
            b.arquivado
                ? Icons.inventory_2_outlined
                : b.encerrado
                ? Icons.flag_outlined
                : Icons.sports_soccer,
          ),
          title: Text(b.name),
          subtitle: Text(
            'Rodadas ${b.matchdayFrom}–${b.matchdayTo} · '
            '${b.totalMembers} participante${b.totalMembers == 1 ? '' : 's'}'
            '${b.encerrado ? ' · encerrado' : ''}'
            '${b.arquivado ? ' · arquivado' : ''}',
          ),
          // Admin é permanente e muda o que a pessoa pode fazer lá dentro:
          // sinalizar aqui evita procurar o botão de convite no bolão errado.
          trailing: b.isAdmin
              ? const Chip(
                  label: Text('admin', style: TextStyle(fontSize: 11)),
                  visualDensity: VisualDensity.compact,
                )
              : const Icon(Icons.chevron_right),
          onTap: () => _abrir(b),
        );
      },
    );
  }
}

class _Centralizado extends StatelessWidget {
  const _Centralizado({
    required this.icone,
    required this.titulo,
    required this.detalhe,
    this.acao,
  });

  final IconData icone;
  final String titulo;
  final String detalhe;
  final Widget? acao;

  @override
  Widget build(BuildContext context) => Center(
    child: Padding(
      padding: const EdgeInsets.all(32),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(icone, size: 56),
          const SizedBox(height: 12),
          Text(
            titulo,
            style: Theme.of(context).textTheme.titleMedium,
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 6),
          Text(detalhe, textAlign: TextAlign.center),
          if (acao != null) ...[const SizedBox(height: 20), acao!],
        ],
      ),
    ),
  );
}

/// Diálogo de entrada por convite.
///
/// Existe como widget próprio para **ser dono** do `TextEditingController`: só
/// o `State` dele sabe quando o campo saiu de fato da árvore, e é aí que o
/// controller pode ser descartado.
class _DialogoDeConvite extends StatefulWidget {
  const _DialogoDeConvite();

  @override
  State<_DialogoDeConvite> createState() => _DialogoDeConviteState();
}

class _DialogoDeConviteState extends State<_DialogoDeConvite> {
  final _controle = TextEditingController();

  @override
  void dispose() {
    _controle.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => AlertDialog(
    title: const Text('Entrar em um bolão'),
    content: Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('Cole o código que o organizador mandou:'),
        const SizedBox(height: 12),
        TextField(
          controller: _controle,
          autofocus: true,
          decoration: const InputDecoration(hintText: 'código do convite'),
          onSubmitted: (v) => Navigator.of(context).pop(v.trim()),
        ),
      ],
    ),
    actions: [
      TextButton(
        onPressed: () => Navigator.of(context).pop(),
        child: const Text('Cancelar'),
      ),
      FilledButton(
        onPressed: () => Navigator.of(context).pop(_controle.text.trim()),
        child: const Text('Entrar'),
      ),
    ],
  );
}
