import unittest
import uuid
from app import create_app
from app.models import db, Usuario, Campanha, Pessoa, MetaCadastrador
from app.services.rede_service import validar_novo_pai
from app.services.cep_service import consultar_cep, cep_cache

class UnityEleitoralTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app()
        self.app.config['TESTING'] = True
        self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
        self.client = self.app.test_client()
        self.app_context = self.app.app_context()
        self.app_context.push()

        db.create_all()

        uid = str(uuid.uuid4())[:8]
        self.dev_email = f"dev_{uid}@test.com"
        self.admin1_email = f"admin1_{uid}@test.com"
        self.admin2_email = f"admin2_{uid}@test.com"
        self.vis_email = f"vis_{uid}@test.com"
        self.cad_email = f"cad_{uid}@test.com"
        
        # Criar Campanha
        campanha = Campanha(
            nome="Campanha Teste 2026",
            politico_principal="Candidato Teste",
            cargo="Deputado",
            municipio="Manaus",
            estado="AM",
            meta_total=1000,
            meta_por_cadastrador=25
        )
        db.session.add(campanha)
        db.session.flush()
        self.campanha_id = campanha.id

        # Criar Usuários
        dev = Usuario(nome="DEV Test", email=self.dev_email, perfil=Usuario.ROLE_DEV, campanha_id=campanha.id, primeiro_acesso=False)
        dev.set_senha("123456")
        
        admin1 = Usuario(nome="Admin 1 Test", email=self.admin1_email, perfil=Usuario.ROLE_ADMIN_1, campanha_id=campanha.id, primeiro_acesso=False)
        admin1.set_senha("123456")

        admin2 = Usuario(nome="Admin 2 Test", email=self.admin2_email, perfil=Usuario.ROLE_ADMIN_2, campanha_id=campanha.id, primeiro_acesso=False)
        admin2.set_senha("123456")

        vis = Usuario(nome="Vis Test", email=self.vis_email, perfil=Usuario.ROLE_VISUALIZADOR, campanha_id=campanha.id, primeiro_acesso=False)
        vis.set_senha("123456")

        cad = Usuario(nome="Cad Test", email=self.cad_email, perfil=Usuario.ROLE_CADASTRADOR, campanha_id=campanha.id, primeiro_acesso=False)
        cad.set_senha("123456")

        db.session.add_all([dev, admin1, admin2, vis, cad])
        db.session.commit()

        self.cad_id = cad.id

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.app_context.pop()

    def login(self, email, senha="123456"):
        return self.client.post('/login', data={'login_input': email, 'senha': senha}, follow_redirects=True)

    def test_login_sucesso(self):
        res = self.login(self.admin1_email)
        self.assertEqual(res.status_code, 200)
        self.assertIn(b"Central de Comando", res.data)

    def test_perfil_visualizador_bloqueio_escrita(self):
        """Valida que o perfil Visualizador não pode realizar cadastros (esperado aviso/bloqueio)"""
        self.login(self.vis_email)
        res = self.client.post('/cadastros/novo', data={
            'nome_completo': 'Pessoa Teste Vis',
            'bairro': 'Centro',
            'zona': 'Zona Norte'
        }, follow_redirects=True)
        
        self.assertIn(b'Perfil Visualizador possui apenas permiss', res.data)

    def test_cadastrador_painel_simplificado(self):
        """Valida que o multiplicador ao logar vai diretamente para o seu painel pessoal"""
        res = self.login(self.cad_email)
        self.assertEqual(res.status_code, 200)
        self.assertIn(b"Meu Painel de Multiplicador", res.data)

    def test_sub_cadastrador_tree(self):
        """Valida a criação de um sub-cadastrador por um cadastrador pai"""
        self.login(self.cad_email)
        sub_email = f"sub_{str(uuid.uuid4())[:6]}@test.com"
        res = self.client.post('/usuarios/novo', data={
            'nome': 'Sub Cadastrador Test',
            'email': sub_email,
            'senha': '123456',
            'perfil': Usuario.ROLE_CADASTRADOR
        }, follow_redirects=True)
        
        self.assertEqual(res.status_code, 200)
        sub_usr = Usuario.query.filter_by(email=sub_email).first()
        self.assertIsNotNone(sub_usr)
        self.assertEqual(sub_usr.cadastrador_pai_id, self.cad_id)

    def test_rn_usu_03_bloqueio_cadastrador_criar_admin(self):
        """RN-USU-03: Cadastrador NÃO pode criar Admin / Dev (esperado 403 Forbidden)"""
        self.login(self.cad_email)
        admin_hack_email = f"hack_{str(uuid.uuid4())[:6]}@test.com"
        res = self.client.post('/usuarios/novo', data={
            'nome': 'Tentativa Admin Hack',
            'email': admin_hack_email,
            'senha': '123456',
            'perfil': Usuario.ROLE_ADMIN
        })
        self.assertEqual(res.status_code, 403)

    def test_validacao_anti_ciclos(self):
        """Valida se o serviço de rede impede ciclos hierárquicos e auto-referência"""
        usr_joao = Usuario(nome="João", email=f"j_{str(uuid.uuid4())[:6]}@test.com", perfil=Usuario.ROLE_CADASTRADOR, campanha_id=self.campanha_id)
        usr_joao.set_senha("123456")
        usr_pedro = Usuario(nome="Pedro", email=f"p_{str(uuid.uuid4())[:6]}@test.com", perfil=Usuario.ROLE_CADASTRADOR, campanha_id=self.campanha_id)
        usr_pedro.set_senha("123456")
        
        db.session.add_all([usr_joao, usr_pedro])
        db.session.commit()

        usr_pedro.cadastrador_pai_id = usr_joao.id
        db.session.commit()

        # 1. Testar auto-referência (João tentar ser pai de si mesmo)
        valido_self, msg = validar_novo_pai(usr_joao.id, usr_joao.id, self.campanha_id)
        self.assertFalse(valido_self)
        self.assertIn("seu próprio superior", msg)

        # 2. Testar ciclo (João tentar colocar Pedro como seu pai, sendo Pedro já seu filho)
        valido_ciclo, msg_ciclo = validar_novo_pai(usr_joao.id, usr_pedro.id, self.campanha_id)
        self.assertFalse(valido_ciclo)
        self.assertIn("Ciclo detectado", msg_ciclo)

    def test_cep_cache_service(self):
        """Valida que o serviço de CEP armazena e recupera do cache"""
        cep_cache.set("69000000", {'sucesso': True, 'bairro': 'Centro', 'zona': 'Zona Norte', 'municipio': 'Manaus'})
        cached_res = cep_cache.get("69000000")
        self.assertIsNotNone(cached_res)
        self.assertEqual(cached_res['bairro'], 'Centro')

    def test_pwa_idempotencia_client_uuid(self):
        """Valida que o envio do mesmo client_uuid não duplica cadastros no PWA sync"""
        self.login(self.cad_email)
        client_uuid_test = f"uuid_{uuid.uuid4()}"
        
        data_sync = {
            'cadastros': [{
                'client_uuid': client_uuid_test,
                'nome_completo': 'Apoiador Repetido PWA',
                'bairro': 'Centro',
                'zona': 'Zona Norte'
            }]
        }

        # Primeiro envio
        res1 = self.client.post('/api/sync/push', json=data_sync)
        self.assertEqual(res1.status_code, 200)
        data1 = res1.get_json()
        self.assertEqual(data1['processados'], 1)

        # Reenvio idêntico (idempotência)
        res2 = self.client.post('/api/sync/push', json=data_sync)
        self.assertEqual(res2.status_code, 200)
        data2 = res2.get_json()
        self.assertEqual(data2['processados'], 0) # Ignorou duplicata

    def test_cadastro_titulo_eleitor(self):
        """Valida que os dados de título de eleitor, zona e seção são gravados corretamente no cadastro"""
        self.login(self.admin1_email)
        res = self.client.post('/cadastros/novo', data={
            'nome_completo': 'Apoiador Com Titulo',
            'telefone': '(92) 99999-1111',
            'bairro': 'Centro',
            'zona': 'Zona Sul',
            'titulo_eleitor': '1234 5678 9012',
            'zona_eleitoral': '032',
            'secao_eleitoral': '0150'
        }, follow_redirects=True)
        
        self.assertEqual(res.status_code, 200)
        p = Pessoa.query.filter_by(nome_completo='Apoiador Com Titulo').first()
        self.assertIsNotNone(p)
        self.assertEqual(p.titulo_eleitor, '1234 5678 9012')
        self.assertEqual(p.zona_eleitoral, '032')
        self.assertEqual(p.secao_eleitoral, '0150')

    def test_painel_multiplicador_arvore_e_metricas(self):
        """Valida se o painel do multiplicador exibe métricas de apoiadores vs multiplicadores e a sub-árvore"""
        self.login(self.cad_email)
        
        # 1. Cadastrar um apoiador direto
        self.client.post('/cadastros/novo', data={
            'nome_completo': 'Apoiador Direto Exemplo',
            'bairro': 'Centro',
            'zona': 'Zona Sul'
        })

        # 2. Cadastrar e converter em Novo Multiplicador
        sub_email = f"multi_{uuid.uuid4().hex[:6]}@test.com"
        self.client.post('/cadastros/novo', data={
            'nome_completo': 'Novo Multiplicador Formado',
            'cpf': '999.888.777-66',
            'bairro': 'Alvorada',
            'zona': 'Centro-Oeste',
            'virar_cadastrador': 'on',
            'email_cadastrador': sub_email
        })

        # 3. Acessar o painel do multiplicador e verificar presença das seções
        res = self.client.get('/cadastros/meus')
        self.assertEqual(res.status_code, 200)
        self.assertIn(b"Meu Painel de Multiplicador", res.data)
        self.assertIn(b"Minha \xc3\x81rvore de Multiplicadores", res.data)
        self.assertIn(b"Novo Multiplicador Formado", res.data)

    def test_multiplicador_primeiro_acesso_flow(self):
        """Valida que o login com CPF no primeiro acesso redireciona para troca obrigatória de senha"""
        self.login(self.admin1_email)
        test_cpf = "11122233344"
        
        # 1. Cadastrar apoiador multiplicador com CPF
        self.client.post('/cadastros/novo', data={
            'nome_completo': 'Multiplicador CPF Teste',
            'cpf': test_cpf,
            'bairro': 'Centro',
            'zona': 'Zona Sul',
            'virar_cadastrador': 'on'
        })
        
        # 2. Tentar login usando CPF como usuário e senha inicial CPF
        res_login = self.client.post('/login', data={'login_input': test_cpf, 'senha': test_cpf}, follow_redirects=True)
        self.assertEqual(res_login.status_code, 200)
        self.assertIn(b"Primeiro Acesso ao Sistema", res_login.data)
        
        # 3. Redefinir nova senha no primeiro acesso
        res_pass = self.client.post('/primeiro-acesso', data={
            'nova_senha': 'nova_senha_segura_123',
            'confirmar_senha': 'nova_senha_segura_123'
        }, follow_redirects=True)
        
        self.assertEqual(res_pass.status_code, 200)
        usr = Usuario.query.filter_by(cpf=test_cpf).first()
        self.assertIsNotNone(usr)
        self.assertFalse(usr.primeiro_acesso)
        self.assertTrue(usr.check_senha('nova_senha_segura_123'))

    def test_esqueci_senha_e_reset_admin(self):
        """Valida a solicitação de esqueci a senha e o reset administrativo pelo Admin"""
        test_cpf = "55566677788"
        usr = Usuario(nome="Multiplicador Reset", cpf=test_cpf, perfil=Usuario.ROLE_CADASTRADOR, campanha_id=self.campanha_id)
        usr.set_senha("senha_antiga")
        usr.primeiro_acesso = False
        db.session.add(usr)
        db.session.commit()

        # 1. Solicitar reset de senha
        res_req = self.client.post('/esqueci-senha', data={'identificador': test_cpf}, follow_redirects=True)
        self.assertEqual(res_req.status_code, 200)
        self.assertTrue(usr.solicitou_reset)

        # 2. Admin efetua reset
        self.login(self.admin1_email)
        res_reset = self.client.post(f'/usuarios/{usr.id}/reset-senha', follow_redirects=True)
        self.assertEqual(res_reset.status_code, 200)
        
        # 3. Verificar que a senha voltou a ser o CPF e primeiro_acesso é True
        self.assertTrue(usr.primeiro_acesso)
        self.assertFalse(usr.solicitou_reset)
        self.assertTrue(usr.check_senha(test_cpf))

    def test_voxmanu_branding_and_config(self):
        """Valida se o nome VoxManu e as configurações de marca estão ativas no sistema"""
        self.assertEqual(self.app.config['APP_NAME'], 'VoxManu')
        res = self.client.get('/login')
        self.assertEqual(res.status_code, 200)
        self.assertIn(b'VoxManu', res.data)

    def test_data_nascimento_brazilian_format(self):
        """Valida que o cadastro com data no formato brasileiro DD/MM/AAAA é aceito e convertido corretamente"""
        self.login(self.admin1_email)
        res = self.client.post('/cadastros/novo', data={
            'nome_completo': 'Apoiador Data BR',
            'telefone': '(92)98888-7777',
            'data_nascimento': '15/08/1995',
            'bairro': 'Alvorada',
            'zona': 'Centro-Oeste'
        }, follow_redirects=True)
        
        self.assertEqual(res.status_code, 200)
        p = Pessoa.query.filter_by(nome_completo='Apoiador Data BR').first()
        self.assertIsNotNone(p)
        self.assertEqual(p.data_nascimento.strftime('%d/%m/%Y'), '15/08/1995')

    def test_pedidos_reset_page(self):
        """Valida a página dedicada de solicitações de reset de senha e o contador na sidebar"""
        # Criar usuário solicitando reset
        usr = Usuario(nome="Solicitante Reset Teste", cpf="99900011122", solicitou_reset=True, perfil=Usuario.ROLE_CADASTRADOR, campanha_id=self.campanha_id)
        usr.set_senha("123456")
        db.session.add(usr)
        db.session.commit()

        self.login(self.admin1_email)
        res = self.client.get('/usuarios/pedidos-reset')
        self.assertEqual(res.status_code, 200)
        self.assertIn(b"Solicita\xc3\xa7\xc3\xb5es de Redefini\xc3\xa7\xc3\xa3o de Senha", res.data)
        self.assertIn(b"Solicitante Reset Teste", res.data)

    def test_login_falha_prg_sem_vazamento_sessao(self):
        """Valida que falha de login limpa sessão, usa PRG (evita ERR_CACHE_MISS) e não renderiza sidebar"""
        res = self.login("email_inexistente@test.com", "senha_errada")
        self.assertEqual(res.status_code, 200)
        self.assertIn(b"CPF/E-mail ou senha incorretos.", res.data)
        # Garante que a barra lateral de usuário logado NÃO é renderizada na tela de login
        self.assertNotIn(b"sidebar-menu", res.data)

    def test_admin_meta_customizada_multiplicador(self):
        """Valida que o Admin pode definir e alterar a meta personalizada (ex: 250) de um multiplicador"""
        self.login(self.admin1_email)
        multi_email = f"multi_meta_{uuid.uuid4().hex[:6]}@test.com"
        
        # Criar multiplicador com meta customizada 250
        res = self.client.post('/usuarios/novo', data={
            'nome': 'Multiplicador Meta 250',
            'email': multi_email,
            'senha': '123456',
            'perfil': Usuario.ROLE_CADASTRADOR,
            'meta_custom': '250'
        }, follow_redirects=True)
        self.assertEqual(res.status_code, 200)
        
        usr = Usuario.query.filter_by(email=multi_email).first()
        self.assertIsNotNone(usr)
        meta_obj = MetaCadastrador.query.filter_by(cadastrador_id=usr.id).first()
        self.assertIsNotNone(meta_obj)
        self.assertEqual(meta_obj.meta, 250)

    def test_manual_restricao_por_perfil(self):
        """Valida que o multiplicador vê apenas o seu manual e que o Admin vê todos os manuais"""
        # 1. Multiplicador acessa manual
        self.login(self.cad_email)
        res_cad = self.client.get('/manual')
        self.assertEqual(res_cad.status_code, 200)
        self.assertIn(b"Guia do Multiplicador", res_cad.data)
        self.assertNotIn(b"Guia do Administrador", res_cad.data)
        self.assertNotIn(b"tab-btn", res_cad.data) # Abas ocultas

        # 2. Administrador acessa manual
        self.login(self.admin1_email)
        res_admin = self.client.get('/manual')
        self.assertEqual(res_admin.status_code, 200)
        self.assertIn(b"Guia do Administrador", res_admin.data)
        self.assertIn(b"Guia do Multiplicador", res_admin.data)
        self.assertIn(b"tab-btn", res_admin.data) # Abas visíveis para alternar

    def test_territorio_api_pontos_mapeamento(self):
        """Valida que o endpoint de território plota 100% dos apoiadores cadastrados por bairro no mapa"""
        self.login(self.admin1_email)
        
        # Cadastrar 2 apoiadores no bairro Alvorada sem lat/lng individuais
        self.client.post('/cadastros/novo', data={
            'nome_completo': 'Apoiador Alvorada 1',
            'bairro': 'Alvorada',
            'zona': 'Centro-Oeste'
        })
        self.client.post('/cadastros/novo', data={
            'nome_completo': 'Apoiador Alvorada 2',
            'bairro': 'Alvorada',
            'zona': 'Centro-Oeste'
        })
        
        res = self.client.get('/territorio/api/pontos?bairro=Alvorada')
        self.assertEqual(res.status_code, 200)
        data = res.get_json()
        self.assertGreaterEqual(data['total'], 2)
        # Verifica se os pontos possuem latitude e longitude calculadas
        ponto1 = data['pontos'][0]
        self.assertIsNotNone(ponto1['lat'])
        self.assertIsNotNone(ponto1['lng'])
        self.assertEqual(ponto1['bairro'], 'Alvorada')

    def test_api_nao_autenticado_retorna_json_401(self):
        """Valida que chamadas de API deslogadas retornam JSON 401 e nunca HTML da tela de login"""
        # Limpar sessão para simular deslogado
        self.client.get('/logout')
        res = self.client.get('/territorio/api/pontos', headers={'Accept': 'application/json'})
        self.assertEqual(res.status_code, 401)
        self.assertTrue(res.is_json)
        data = res.get_json()
        self.assertEqual(data['error'], 'Não autenticado')

if __name__ == '__main__':
    unittest.main()
