import copy
import os
import unittest

import lib.versoes_sistemas as versoes_sistemas
from lib.registro_docker import ErroRegistroDocker
from lib.versoes_sistemas import (
    ErroVersaoSistema,
    preparar_versao_sistema,
    resolver_versao_sistema,
    validar_todos_os_sistemas,
)

DIRETORIO_SCRIPTS_IMPLANTACAO = os.path.abspath(
    os.path.join(os.path.dirname(__file__), "..", "..")
)

CONFIGURACAO_BASE = {
    "acessos": {
        "servidorLinux": {"configurar": True, "usuario": "deploy"},
        "registroDocker": {"usuario": "oauth2", "senha": "token"},
    },
    "enterprise": {
        "configurar": True,
        "imagem": {
            "nome": "gitlab.microuniverso.com.br:5050/microuniverso/mufenix/enterprise/enterprise",
            "versao": "5.4.9-linux",
            "usuario": "oauth2",
            "senha": "token-especifico",
        },
        "renderizador": {
            "configurar": False,
            "imagem": {
                "nome": "gitlab.microuniverso.com.br:5050/microuniverso/mufenix/enterprise/renderizador"
            },
        },
    },
    "centralDeRelatorios": {
        "configurar": True,
        "versao": "1.6.0",
        "storybook": {"configurar": False, "versao": "1.0.0"},
    },
}

TAGS_POR_REPOSITORIO = {
    "microuniverso/mufenix/enterprise/enterprise": [
        "5.4.9-linux", "5.4.7-linux", "5.4.5-linux", "FNX-1234-linux", "master",
    ],
    "microuniverso/mufenix/enterprise/renderizador": ["5.4.9-linux", "5.4.7-linux"],
    "microuniverso/mufenix/centralrelatorios/central-de-relatorios": [
        "1.6.0", "1.5.0", "1.4.4", "476443-Centaurus-ContasAReceber",
    ],
}


class RegistroFalso():
    """Substitui o acesso ao registro nos testes."""

    def __init__(self, tags_por_repositorio=None, erro=None):
        self.tags_por_repositorio = (
            TAGS_POR_REPOSITORIO if tags_por_repositorio is None else tags_por_repositorio
        )
        self.erro = erro
        self.consultas_de_tag = []

    def listar_tags(self, repositorio):
        if self.erro:
            raise self.erro

        if repositorio not in self.tags_por_repositorio:
            raise ErroRegistroDocker(f"Repositorio {repositorio} nao encontrado.")

        return list(self.tags_por_repositorio[repositorio])

    def tag_existe(self, repositorio, tag):
        if self.erro:
            raise self.erro

        self.consultas_de_tag.append((repositorio, tag))

        return tag in self.tags_por_repositorio.get(repositorio, [])


class BaseVersoesSistemas(unittest.TestCase):
    def setUp(self):
        versoes_sistemas.limpar_caches()

        self.registro = RegistroFalso()
        self._obter_registro_original = versoes_sistemas._obter_registro

        def obter_registro_falso(sistema, configuracoes):
            imagem = sistema.obter_imagem(configuracoes)
            _, repositorio, _ = versoes_sistemas.separar_referencia_imagem(imagem)
            return self.registro, repositorio, imagem

        versoes_sistemas._obter_registro = obter_registro_falso

    def tearDown(self):
        versoes_sistemas._obter_registro = self._obter_registro_original
        versoes_sistemas.limpar_caches()

    def montar_configuracao(self, **alteracoes):
        configuracoes = copy.deepcopy(CONFIGURACAO_BASE)

        if "versao_enterprise" in alteracoes:
            configuracoes["enterprise"]["imagem"]["versao"] = alteracoes["versao_enterprise"]

        if "versao_relatorios" in alteracoes:
            configuracoes["centralDeRelatorios"]["versao"] = alteracoes["versao_relatorios"]

        if "configurar_enterprise" in alteracoes:
            configuracoes["enterprise"]["configurar"] = alteracoes["configurar_enterprise"]

        if "configurar_relatorios" in alteracoes:
            configuracoes["centralDeRelatorios"]["configurar"] = alteracoes["configurar_relatorios"]

        return configuracoes


class TestResolverMaster(BaseVersoesSistemas):
    def test_master_resolve_para_versao_oficial_mais_recente(self):
        configuracoes = self.montar_configuracao(versao_relatorios="master")

        self.assertEqual(
            resolver_versao_sistema(configuracoes, "centralDeRelatorios"),
            "1.6.0"
        )

    def test_master_da_enterprise_respeita_a_plataforma_linux(self):
        configuracoes = self.montar_configuracao(versao_enterprise="master")

        self.assertEqual(resolver_versao_sistema(configuracoes, "enterprise"), "5.4.9-linux")

    def test_latest_tambem_resolve(self):
        configuracoes = self.montar_configuracao(versao_relatorios="latest")

        self.assertEqual(resolver_versao_sistema(configuracoes, "centralDeRelatorios"), "1.6.0")

    def test_master_sem_versao_oficial_publicada_gera_erro(self):
        self.registro = RegistroFalso(
            tags_por_repositorio={
                "microuniverso/mufenix/centralrelatorios/central-de-relatorios": ["master", "FNX-1"]
            }
        )
        configuracoes = self.montar_configuracao(versao_relatorios="master")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "centralDeRelatorios")

        self.assertIn("nenhuma versao oficial publicada", str(contexto.exception))

    def test_registro_indisponivel_com_master_gera_erro(self):
        self.registro = RegistroFalso(erro=ErroRegistroDocker("rede fora do ar"))
        configuracoes = self.montar_configuracao(versao_relatorios="master")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "centralDeRelatorios")

        self.assertIn("rede fora do ar", str(contexto.exception))

    def test_versao_resolvida_e_cacheada(self):
        configuracoes = self.montar_configuracao(versao_relatorios="master")

        primeira = resolver_versao_sistema(configuracoes, "centralDeRelatorios")
        configuracoes["centralDeRelatorios"]["versao"] = "1.5.0"
        segunda = resolver_versao_sistema(configuracoes, "centralDeRelatorios")

        self.assertEqual(primeira, segunda)


class TestValidacaoDaImagem(BaseVersoesSistemas):
    def test_versao_existente_e_aceita(self):
        configuracoes = self.montar_configuracao(versao_relatorios="1.5.0")

        self.assertEqual(resolver_versao_sistema(configuracoes, "centralDeRelatorios"), "1.5.0")

    def test_versao_inexistente_gera_erro_com_sugestoes(self):
        configuracoes = self.montar_configuracao(versao_relatorios="9.9.9")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "centralDeRelatorios")

        mensagem = str(contexto.exception)

        self.assertIn("nao existe no registro", mensagem)
        self.assertIn("9.9.9", mensagem)
        self.assertIn("1.6.0", mensagem)

    def test_erro_aponta_o_campo_da_configuracao(self):
        configuracoes = self.montar_configuracao(versao_enterprise="9.9.9-linux")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "enterprise")

        self.assertIn("Corrija em: enterprise.imagem.versao", str(contexto.exception))
        self.assertEqual(contexto.exception.caminho_configuracao, "enterprise.imagem.versao")

    def test_erro_nomeia_o_sistema_na_primeira_linha(self):
        configuracoes = self.montar_configuracao(versao_enterprise="9.9.9-linux")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "enterprise")

        primeira_linha = str(contexto.exception).splitlines()[0]

        self.assertTrue(primeira_linha.startswith("API Enterprise:"), primeira_linha)
        self.assertIn("nao existe no registro", primeira_linha)

    def test_sugestoes_nao_incluem_tags_de_branch_ou_teste(self):
        configuracoes = self.montar_configuracao(versao_enterprise="9.9.9-linux")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "enterprise")

        mensagem = str(contexto.exception)

        self.assertIn("5.4.9-linux", mensagem)
        self.assertNotIn("FNX-1234-linux", mensagem)
        self.assertNotIn("master", mensagem.replace("Ou use 'master'", ""))

    def test_sugestoes_respeitam_a_plataforma_do_sistema(self):
        self.registro = RegistroFalso(
            tags_por_repositorio={
                "microuniverso/mufenix/enterprise/enterprise": [
                    "5.4.9-linux", "5.5.0-windows", "5.4.7-linux",
                ]
            }
        )
        configuracoes = self.montar_configuracao(versao_enterprise="9.9.9-linux")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "enterprise")

        mensagem = str(contexto.exception)

        self.assertIn("5.4.9-linux", mensagem)
        self.assertNotIn("5.5.0-windows", mensagem)

    def test_erro_avisa_quando_nao_ha_versao_oficial_para_sugerir(self):
        self.registro = RegistroFalso(
            tags_por_repositorio={
                "microuniverso/mufenix/centralrelatorios/central-de-relatorios": ["FNX-1", "master"]
            }
        )
        configuracoes = self.montar_configuracao(versao_relatorios="9.9.9")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "centralDeRelatorios")

        self.assertIn("Nenhuma versao oficial", str(contexto.exception))

    def test_registro_indisponivel_apenas_avisa_para_versao_concreta(self):
        self.registro = RegistroFalso(erro=ErroRegistroDocker("registro fora do ar"))
        configuracoes = self.montar_configuracao(versao_relatorios="1.5.0")

        self.assertEqual(resolver_versao_sistema(configuracoes, "centralDeRelatorios"), "1.5.0")

    def test_valor_de_modelo_gera_erro_claro(self):
        configuracoes = self.montar_configuracao(versao_relatorios="VERSAO_CENTRAL_DE_RELATORIOS")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "centralDeRelatorios")

        self.assertIn("valor de modelo", str(contexto.exception))

    def test_versao_vazia_gera_erro(self):
        configuracoes = self.montar_configuracao(versao_relatorios="")

        with self.assertRaises(ErroVersaoSistema) as contexto:
            resolver_versao_sistema(configuracoes, "centralDeRelatorios")

        self.assertIn("nao esta informada", str(contexto.exception))

    def test_validacao_pode_ser_desligada(self):
        configuracoes = self.montar_configuracao(versao_relatorios="9.9.9")

        self.assertEqual(
            resolver_versao_sistema(configuracoes, "centralDeRelatorios", validar_imagem=False),
            "9.9.9"
        )


class TestMapaDependenciasIntegrado(BaseVersoesSistemas):
    def test_par_valido_passa(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="5.4.9-linux",
            versao_relatorios="1.6.0"
        )

        self.assertEqual(
            preparar_versao_sistema(configuracoes, "centralDeRelatorios", DIRETORIO_SCRIPTS_IMPLANTACAO),
            "1.6.0"
        )

    def test_enterprise_nova_com_relatorios_antigo_bloqueia(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="5.4.9-linux",
            versao_relatorios="1.5.0"
        )

        with self.assertRaises(ErroVersaoSistema) as contexto:
            preparar_versao_sistema(configuracoes, "centralDeRelatorios", DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertIn("Combinacao de versoes invalida", str(contexto.exception))

    def test_relatorios_novo_com_enterprise_antiga_bloqueia(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="5.4.7-linux",
            versao_relatorios="1.6.0"
        )

        with self.assertRaises(ErroVersaoSistema) as contexto:
            preparar_versao_sistema(configuracoes, "enterprise", DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertIn("Combinacao de versoes invalida", str(contexto.exception))

    def test_sistema_vizinho_desligado_nao_bloqueia(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="5.4.9-linux",
            versao_relatorios="1.5.0",
            configurar_enterprise=False
        )

        self.assertEqual(
            preparar_versao_sistema(configuracoes, "centralDeRelatorios", DIRETORIO_SCRIPTS_IMPLANTACAO),
            "1.5.0"
        )

    def test_master_nos_dois_lados_resulta_em_par_valido(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="master",
            versao_relatorios="master"
        )

        self.assertEqual(
            preparar_versao_sistema(configuracoes, "centralDeRelatorios", DIRETORIO_SCRIPTS_IMPLANTACAO),
            "1.6.0"
        )
        self.assertEqual(
            preparar_versao_sistema(configuracoes, "enterprise", DIRETORIO_SCRIPTS_IMPLANTACAO),
            "5.4.9-linux"
        )

    def test_master_de_um_lado_com_versao_antiga_do_outro_bloqueia(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="master",
            versao_relatorios="1.5.0"
        )

        with self.assertRaises(ErroVersaoSistema) as contexto:
            preparar_versao_sistema(configuracoes, "centralDeRelatorios", DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertIn("Combinacao de versoes invalida", str(contexto.exception))


class TestValidarTodosOsSistemas(BaseVersoesSistemas):
    def test_combinacao_valida_nao_retorna_erro(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="5.4.9-linux",
            versao_relatorios="1.6.0"
        )

        versoes, erros = validar_todos_os_sistemas(configuracoes, DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertEqual(erros, [])
        self.assertEqual(versoes["enterprise"], "5.4.9-linux")
        self.assertEqual(versoes["centralDeRelatorios"], "1.6.0")

    def test_combinacao_invalida_retorna_erro(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="5.4.9-linux",
            versao_relatorios="1.5.0"
        )

        _, erros = validar_todos_os_sistemas(configuracoes, DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertEqual(len(erros), 1)
        self.assertIn("Combinacao de versoes invalida", erros[0])

    def test_enterprise_e_renderizador_geram_um_unico_erro(self):
        # Os dois leem enterprise.imagem.versao, entao uma versao errada tem uma
        # causa raiz so e nao deve aparecer duas vezes no log.
        configuracoes = self.montar_configuracao(
            versao_enterprise="475551_Centaurus_RestruturacaoVisual_CentralDeCadastros-linux"
        )
        configuracoes["enterprise"]["renderizador"]["configurar"] = True

        _, erros = validar_todos_os_sistemas(configuracoes, DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertEqual(len(erros), 1, erros)
        self.assertIn("API Enterprise", erros[0])
        self.assertNotIn("Renderizador", erros[0])

    def test_erros_de_campos_diferentes_nao_sao_agrupados(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="9.9.9-linux",
            versao_relatorios="9.9.9"
        )

        _, erros = validar_todos_os_sistemas(configuracoes, DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertEqual(len(erros), 2, erros)

    def test_imagem_inexistente_entra_na_lista_de_erros(self):
        configuracoes = self.montar_configuracao(
            versao_enterprise="5.4.9-linux",
            versao_relatorios="9.9.9"
        )

        versoes, erros = validar_todos_os_sistemas(configuracoes, DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertNotIn("centralDeRelatorios", versoes)
        self.assertTrue(any("nao existe no registro" in erro for erro in erros))

    def test_sistemas_desligados_ficam_de_fora(self):
        configuracoes = self.montar_configuracao(
            configurar_enterprise=False,
            configurar_relatorios=False
        )

        versoes, erros = validar_todos_os_sistemas(configuracoes, DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertEqual(versoes, {})
        self.assertEqual(erros, [])

    def test_servidor_linux_desligado_desabilita_todos(self):
        configuracoes = self.montar_configuracao()
        configuracoes["acessos"]["servidorLinux"]["configurar"] = False

        versoes, erros = validar_todos_os_sistemas(configuracoes, DIRETORIO_SCRIPTS_IMPLANTACAO)

        self.assertEqual(versoes, {})
        self.assertEqual(erros, [])


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