"""
Catalogo de tudo que os scripts de implantacao consomem publicado.

Enquanto `sistemas_docker.py` descreve os sistemas do ponto de vista do YAML do
cliente (onde esta a versao, onde esta a chave `configurar`), este catalogo
descreve o ponto de vista do publicador: onde cada coisa e publicada e como
descobrir a ultima versao e a data em que ela foi gerada.

Tres origens sao cobertas:

- `TIPO_DOCKER`:   imagem no registro do GitLab da empresa. A ultima versao vem
                   das tags do repositorio do registro.
- `TIPO_GIT`:      repositorio Git (sites IIS e servicos publicados por branch
                   ou tag, nao por imagem). A ultima versao vem das tags e/ou
                   das branches.
- `TIPO_DOWNLOAD`: arquivo publicado no site de download. A ultima versao vem da
                   listagem de diretorio.

O caminho do projeto no GitLab e derivado da propria imagem: no registro do
GitLab a localizacao e sempre `<host>/<caminho-do-projeto>/<nome-da-imagem>`,
entao `microuniverso/mufenix/enterprise/enterprise` implica no projeto
`microuniverso/mufenix/enterprise`. Isso permite consultar tambem as imagens que
ainda nao tem um `listarImagensETagsExistentes.js` proprio.
"""

import re

from lib.sistemas_docker import REGISTRO_EMPRESA
from lib.versoes_docker import (
    ordenar_tags_da_mais_recente_para_mais_antiga,
    selecionar_versao_oficial_mais_recente,
)


GITLAB = "https://gitlab.microuniverso.com.br"
API_GITLAB = f"{GITLAB}/api/v4"

TIPO_DOCKER = "docker"
TIPO_GIT = "git"
TIPO_DOWNLOAD = "download"


class ProjetoPublicado():
    """Uma coisa publicada: imagem Docker, repositorio Git ou arquivo de download."""

    def __init__(
        self,
        chave,
        nome,
        tipo,
        imagem=None,
        projeto=None,
        repositorio=None,
        plataformas=(),
        storybook=False,
        incluir_branches=False,
        somente_branches=False,
        listagem=None,
        padrao_arquivo=None,
        observacao=None
    ):
        self.chave = chave
        self.nome = nome
        self.tipo = tipo

        # Docker
        self.imagem = imagem
        self.plataformas = tuple(plataformas)

        # Storybook e vitrine de componentes, nao sistema implantado no cliente:
        # fica de fora do inventario por padrao para nao poluir a lista.
        self.storybook = storybook

        # Ids ja usados nos scripts listarImagensETagsExistentes.js. Servem de
        # atalho: quando divergem do que a API devolve, o script avisa.
        self.projeto = projeto
        self.repositorio = repositorio

        # Git
        self.incluir_branches = incluir_branches
        self.somente_branches = somente_branches

        # Download
        self.listagem = listagem
        self.padrao_arquivo = padrao_arquivo

        self.observacao = observacao

    @property
    def caminho_projeto(self):
        """Caminho do projeto no GitLab derivado da imagem (None sem imagem)."""

        return caminho_projeto_da_imagem(self.imagem)

    @property
    def localizacao_esperada(self):
        """Localizacao completa da imagem no registro, como a API a devolve."""

        return None if not self.imagem else f"{REGISTRO_EMPRESA}/{self.imagem}"

    def link_listagem_configurado(self):
        """
        Link de listagem que os scripts atuais usam, quando existe.

        Serve para a saida mostrar o mesmo endereco que esta hoje nos
        listarImagensETagsExistentes.js, mesmo quando a consulta falha.
        """

        if self.tipo == TIPO_DOCKER and self.projeto and self.repositorio:
            return f"{API_GITLAB}/projects/{self.projeto}/registry/repositories/{self.repositorio}/tags"

        if self.tipo == TIPO_GIT and self.projeto:
            recurso = "branches" if self.somente_branches else "tags"

            return f"{API_GITLAB}/projects/{self.projeto}/repository/{recurso}"

        return self.listagem

    def __repr__(self):
        return f"ProjetoPublicado({self.chave!r})"


def caminho_projeto_da_imagem(imagem):
    """
    Caminho do projeto no GitLab a partir do caminho da imagem.

    "microuniverso/mufenix/enterprise/enterprise" -> "microuniverso/mufenix/enterprise"
    """

    if not imagem:
        return None

    partes = [parte for parte in str(imagem).strip().strip("/").split("/") if parte]

    if len(partes) < 2:
        return None

    return "/".join(partes[:-1])


def selecionar_ultima_versao(tags, plataforma=None):
    """
    Ultima versao de uma lista de tags.

    Prioriza versao oficial (numerica, sem pre-lancamento e sem sufixo de
    branch). Se nao houver nenhuma - caso dos projetos publicados por branch,
    como o Whatsapp Bot -, cai para a tag mais recente pela ordenacao geral,
    porque devolver nada seria pior que devolver o nome da branch.
    """

    oficial = selecionar_versao_oficial_mais_recente(tags, plataforma)

    if oficial:
        return oficial

    if plataforma:
        return None

    ordenadas = ordenar_tags_da_mais_recente_para_mais_antiga(tags)

    return ordenadas[0] if ordenadas else None


def extrair_versoes_da_listagem(html, padrao):
    """
    Versoes de uma listagem de diretorio HTTP.

    Espera um padrao com dois grupos: (arquivo, versao). Devolve a lista de
    pares (arquivo, versao) ordenada da versao mais recente para a mais antiga.
    """

    encontrados = re.findall(padrao, html or "")

    ordenados = sorted(
        encontrados,
        key=lambda par: _chave_numerica(par[1]),
        reverse=True
    )

    return [(arquivo, versao) for arquivo, versao in ordenados]


def _chave_numerica(versao):
    return tuple(int(parte) for parte in re.findall(r"\d+", str(versao))) or (0,)


CATALOGO = (
    ProjetoPublicado(
        chave="enterprise",
        nome="API Enterprise",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/enterprise/enterprise",
        projeto=49,
        repositorio=5,
        plataformas=("linux", "windows"),
    ),
    ProjetoPublicado(
        chave="enterpriseRenderizador",
        nome="Renderizador da API Enterprise",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/enterprise/renderizador",
        projeto=49,
    ),
    ProjetoPublicado(
        chave="centralDeCadastros",
        nome="Central de Cadastros",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/centraldecadastrosfrontend/central-de-cadastros-producao",
        projeto=58,
        repositorio=6,
    ),
    ProjetoPublicado(
        chave="centralDeCadastrosStorybook",
        nome="Storybook da Central de Cadastros",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/centraldecadastrosfrontend/central-de-cadastros-storybook",
        projeto=58,
        repositorio=9,
        storybook=True,
    ),
    ProjetoPublicado(
        chave="centralDeRelatorios",
        nome="Central de Relatorios",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/centralrelatorios/central-de-relatorios",
        projeto=103,
        repositorio=16,
    ),
    ProjetoPublicado(
        chave="centralDeRelatoriosStorybook",
        nome="Storybook da Central de Relatorios",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/centralrelatorios/central-de-relatorios-storybook",
        projeto=103,
        storybook=True,
    ),
    ProjetoPublicado(
        chave="pesquisaProduto",
        nome="Pesquisa de Produto",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/pesquisaproduto/pesquisa-produto",
        projeto=88,
        repositorio=7,
    ),
    ProjetoPublicado(
        chave="coletaPrecos",
        nome="Coleta de Precos",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/coletaprecos/coleta-preco",
        projeto=97,
        repositorio=15,
    ),
    ProjetoPublicado(
        chave="coletaPrecosStorybook",
        nome="Storybook da Coleta de Precos",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/coletaprecos/coleta-preco-storybook",
        projeto=97,
        repositorio=5,
        storybook=True,
    ),
    ProjetoPublicado(
        chave="intercompany",
        nome="Intercompany",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/intercompanyfrontend/inter-company-frontend-releases",
        projeto=93,
        repositorio=13,
    ),
    ProjetoPublicado(
        chave="intercompanyStorybook",
        nome="Storybook do Intercompany",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/intercompanyfrontend/inter-company-storybook-releases",
        projeto=93,
        repositorio=14,
        storybook=True,
    ),
    ProjetoPublicado(
        chave="configuracoesWeb",
        nome="Configuracoes Web",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/configuracoesweb/configuracoes-web",
        projeto=114,
        repositorio=23,
    ),
    ProjetoPublicado(
        chave="muWhatsapp",
        nome="Mu Whatsapp API",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/wppconnect-server/muwhatsapp",
        projeto=110,
        repositorio=20,
    ),
    ProjetoPublicado(
        chave="microkafka",
        nome="Microkafka",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/microkafka/microkafka",
        projeto=33,
        repositorio=3,
    ),
    ProjetoPublicado(
        chave="microkafkaConfigurador",
        nome="Configurador do Microkafka",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/microkafka/configurar-microkafka",
        projeto=33,
        repositorio=4,
    ),
    ProjetoPublicado(
        chave="vendasStorybook",
        nome="Storybook de Vendas (Orcamento Web)",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/projetoweb/vendas-storybook",
        projeto=78,
        repositorio=8,
        storybook=True,
    ),
    ProjetoPublicado(
        chave="cassini",
        nome="Cassini",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/cassini/cassini",
    ),
    ProjetoPublicado(
        chave="proxyHttpsLinux",
        nome="Proxy HTTPS Linux",
        tipo=TIPO_DOCKER,
        imagem="microuniverso/mufenix/scriptsdeploy/proxyhttpslinux",
    ),
    ProjetoPublicado(
        chave="releaseWeb",
        nome="Microuniverso Web / Sites IIS (ReleaseWeb)",
        tipo=TIPO_GIT,
        projeto=77,
        incluir_branches=True,
    ),
    ProjetoPublicado(
        chave="muDllRest",
        nome="MuDllRest",
        tipo=TIPO_GIT,
        projeto=90,
    ),
    ProjetoPublicado(
        chave="whatsappBot",
        nome="Whatsapp Bot",
        tipo=TIPO_GIT,
        projeto=19,
        somente_branches=True,
    ),
    ProjetoPublicado(
        chave="srvcusto",
        nome="SRVCUSTO (utilitario TradSql)",
        tipo=TIPO_DOWNLOAD,
        listagem="http://download.microuniverso.com.br/TradSql/Utilitarios/SRVCUSTO/",
        padrao_arquivo=r'href="(srvcusto(\d+)\.zip)"',
    ),
)

CATALOGO_POR_CHAVE = {projeto.chave: projeto for projeto in CATALOGO}

# Sistemas sem endereco fixo no repositorio: o endereco vem do YAML do cliente
# ou a imagem e publica. Ficam de fora da consulta, mas aparecem na saida para o
# inventario nao dar a impressao de estar incompleto.
SEM_ENDERECO_FIXO = (
    ProjetoPublicado(
        chave="monitorFreteRapido",
        nome="Monitor Frete Rapido",
        tipo=TIPO_DOCKER,
        observacao="imagem e versao vem de monitorFreteRapido.imagem.nome / .versao no YAML do cliente",
    ),
    ProjetoPublicado(
        chave="mongoDbSitesIIS",
        nome="MongoDB dos Sites IIS",
        tipo=TIPO_DOCKER,
        observacao="usa a imagem publica mongo:4.4",
    ),
    ProjetoPublicado(
        chave="apiGateway",
        nome="API Gateway",
        tipo=TIPO_DOCKER,
        observacao="usa a imagem publica nginx:1.26.0",
    ),
)


def filtrar_catalogo(filtro=None, incluir_storybooks=False):
    """
    Entradas do catalogo a consultar.

    Storybook so entra com `incluir_storybooks=True`, inclusive quando o texto
    do filtro casaria com ele: a flag manda, para o resultado nao depender de
    como o filtro foi escrito.
    """

    entradas = [
        projeto for projeto in CATALOGO
        if incluir_storybooks or not projeto.storybook
    ]

    if not filtro:
        return entradas

    alvo = str(filtro).strip().lower()

    return [
        projeto for projeto in entradas
        if alvo in projeto.chave.lower()
        or alvo in projeto.nome.lower()
        or alvo in str(projeto.imagem or "").lower()
    ]


def ha_storybook_correspondente(filtro):
    """
    Indica se algum storybook casaria com o filtro.

    Usado so para a mensagem de "nada encontrado" poder sugerir a flag em vez de
    deixar o usuario achando que o sistema nao existe no catalogo.
    """

    alvo = "" if not filtro else str(filtro).strip().lower()

    return any(
        projeto.storybook and (
            not alvo
            or alvo in projeto.chave.lower()
            or alvo in projeto.nome.lower()
            or alvo in str(projeto.imagem or "").lower()
        )
        for projeto in CATALOGO
    )
