import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../core/api/api_client.dart';
import '../../../core/sessao/sessao_store.dart';
import '../data/auth_repository.dart';
import '../../../core/firebase/provedores.dart';

/// Os provedores mudaram de casa em `MG-7` (`core/firebase/provedores.dart`),
/// onde a escolha entre HTTP e Firebase é feita. Reexportados aqui para a
/// apresentação e os testes continuarem importando do mesmo lugar de sempre —
/// a troca de backend não tem por que aparecer na tela.
export '../../../core/firebase/provedores.dart'
    show sessaoStoreProvider, apiClientProvider, authRepositoryProvider;

/// Estado da sessão. Sem code generation: `Notifier` escrito à mão, porque
/// `riverpod_generator` e `freezed` não coexistem nesta versão do analyzer.
sealed class EstadoSessao {
  const EstadoSessao();
}

class SessaoCarregando extends EstadoSessao {
  const SessaoCarregando();
}

class SemSessao extends EstadoSessao {
  const SemSessao();
}

class Autenticado extends EstadoSessao {
  const Autenticado(this.conta);
  final Conta conta;
}

class SessaoNotifier extends Notifier<EstadoSessao> {
  @override
  EstadoSessao build() {
    // Restaura no cold start: o participante não pode encontrar tela de login
    // justamente quando corre para palpitar.
    Future.microtask(restaurar);
    return const SessaoCarregando();
  }

  AuthRepository get _repo => ref.read(authRepositoryProvider);
  SessaoStore get _store => ref.read(sessaoStoreProvider);

  Future<void> restaurar() async {
    // Dependências capturadas **antes** dos gaps assíncronos: `ref.read` depois
    // que o provider foi descartado (app fechando no meio da restauração)
    // lança, e o erro não teria onde ser tratado.
    final store = _store;
    final repo = _repo;

    final token = await store.accessToken;
    // `ref.mounted` depois de cada gap: o app pode ser fechado no meio da
    // restauração, e escrever `state` num notifier descartado lança um erro
    // assíncrono que ninguém trata.
    if (!ref.mounted) return;
    if (token == null) {
      state = const SemSessao();
      return;
    }
    try {
      final conta = await repo.minhaConta();
      await _guardarConta(conta, store);
      if (!ref.mounted) return;
      state = Autenticado(conta);
    } on ApiError catch (e) {
      // Sem conexão **não** é sessão inválida. O comentário já dizia isso e o
      // código fazia o contrário: os dois ramos deslogavam. Abrir o app no
      // ônibus, sem rede, jogava o participante no login — justamente quando
      // ele tem palpite guardado para enviar, e sem recuperação de senha para
      // voltar. Agora entra com a conta em cache e revalida quando a rede volta.
      if (e.semConexao) {
        final cache = await store.conta;
        if (!ref.mounted) return;
        state = cache == null
            ? const SemSessao()
            : Autenticado(Conta.doJson(cache));
      } else {
        await store.limpar();
        if (!ref.mounted) return;
        state = const SemSessao();
      }
    }
  }

  Future<void> _guardarConta(Conta conta, [SessaoStore? store]) =>
      (store ?? _store).salvarConta({
        'id': conta.id,
        'email': conta.email,
        'displayName': conta.displayName,
      });

  Future<void> entrar({required String email, required String senha}) async {
    await _repo.entrar(email: email, senha: senha);
    final conta = await _repo.minhaConta();
    await _guardarConta(conta);
    state = Autenticado(conta);
  }

  Future<Conta> cadastrar({
    required String email,
    required String senha,
    required String nomeExibicao,
  }) async {
    final conta = await _repo.cadastrar(
      email: email,
      senha: senha,
      nomeExibicao: nomeExibicao,
    );
    // Entra na sequência: o convidado precisa chegar ao bolão sem passo extra.
    await _repo.entrar(email: email, senha: senha);
    await _guardarConta(conta);
    state = Autenticado(conta);
    return conta;
  }

  Future<void> sair() async {
    await _repo.sair();
    state = const SemSessao();
  }
}

final sessaoProvider = NotifierProvider<SessaoNotifier, EstadoSessao>(
  SessaoNotifier.new,
);
