import argparse
import getpass
import sys

from custos_precificacao import auth, cadastro, planejamento, vinculos
from custos_precificacao.custeio import executar_custeio
from custos_precificacao.db import SessionLocal
from custos_precificacao.despesas import calcular_alocacao_despesa
from custos_precificacao.models import CenarioPrecificacao, CusteioProduto, Despesa, Produto, Usuario


def custeio_run(cenario_codigo: str, observacao: str | None, competencia: str | None = None) -> None:
    with SessionLocal() as session:
        cenario = session.query(CenarioPrecificacao).filter_by(codigo=cenario_codigo).one()
        execucao = executar_custeio(session, cenario, observacao=observacao, competencia=competencia)
        produtos = {p.id: p for p in session.query(Produto).all()}
        resultados = (
            session.query(CusteioProduto).filter(CusteioProduto.execucao_id == execucao.id).all()
        )

        print(
            f"Execução #{execucao.id} em {execucao.executado_em} | cenário={cenario.codigo} "
            f"| competência={execucao.competencia:%Y-%m} "
            f"| custo_fixo_nao_rateado={execucao.custo_fixo_nao_rateado:.4f}"
        )
        for r in resultados:
            produto = produtos[r.produto_id]
            print(
                f"  {produto.codigo:10s} {produto.nome:40.40s} "
                f"maquina={r.custo_maquina:.4f} ambiente={r.custo_ambiente:.4f} "
                f"mao_de_obra={r.custo_mao_de_obra:.4f} outros={r.custo_outros:.4f} insumos={r.custo_insumos:.4f} "
                f"-> producao_unitario={r.custo_producao_unitario:.4f} "
                f"variavel_unitario={r.custo_variavel_unitario:.4f}"
            )


def _parse_valores(pares: list[str]) -> dict[str, str]:
    return dict(item.split("=", 1) for item in pares)


def cadastro_cmd(args: argparse.Namespace) -> None:
    entidade = cadastro.resolver_entidade(args.entidade)
    with SessionLocal() as session:
        if args.acao == "list":
            for instancia in cadastro.listar(session, entidade):
                print(cadastro.formatar(instancia))
        elif args.acao == "get":
            print(cadastro.formatar(cadastro.obter(session, entidade, args.chave)))
        elif args.acao == "create":
            instancia = cadastro.criar(session, entidade, _parse_valores(args.valores))
            print(cadastro.formatar(instancia))
        elif args.acao == "update":
            instancia = cadastro.atualizar(
                session, entidade, args.chave, _parse_valores(args.valores), atualizado_em=args.atualizado_em
            )
            print(cadastro.formatar(instancia))
        elif args.acao == "delete":
            cadastro.deletar(session, entidade, args.chave)
            print(f"{args.entidade} {args.chave!r} apagado")


def roteiro_cmd(args: argparse.Namespace) -> None:
    with SessionLocal() as session:
        if args.acao in ("add-maquina", "add-ambiente", "add-mao-de-obra"):
            tipo = args.acao.removeprefix("add-")
            linha = vinculos.adicionar_roteiro_recurso(
                session, tipo, args.produto, args.atividade, args.recurso, args.tempo_horas,
                atualizado_em=args.atualizado_em,
            )
            print(f"Linha de roteiro criada: id={linha.id}")
        elif args.acao == "add-insumo":
            linha = vinculos.adicionar_roteiro_insumo(
                session,
                args.produto,
                args.atividade,
                args.quantidade_kg,
                insumo_codigo=args.insumo,
                insumo_produto_codigo=args.insumo_produto,
                atualizado_em=args.atualizado_em,
            )
            print(f"Linha de roteiro criada: id={linha.id}")
        elif args.acao == "list":
            roteiro = vinculos.listar_roteiro(session, args.produto)
            for tipo, linhas in roteiro.items():
                for linha in linhas:
                    print(f"{tipo}: {cadastro.formatar(linha)}")
        elif args.acao == "remove":
            arquivada = vinculos.remover_linha_roteiro(session, args.tipo, args.linha_id, em=args.em)
            print(f"Linha de roteiro {args.tipo} id={args.linha_id} removida" + (" (guardada no histórico)" if arquivada else ""))
        elif args.acao == "alterar":
            linha = vinculos.alterar_linha_roteiro(
                session, args.tipo, args.linha_id, args.quantidade, atualizado_em=args.atualizado_em
            )
            print(f"Linha de roteiro {args.tipo} id={linha.id} atualizada em {linha.atualizado_em:%Y-%m}")


def cenario_cmd(args: argparse.Namespace) -> None:
    with SessionLocal() as session:
        if args.acao == "desligar":
            vinculos.desligar_recurso(session, args.cenario, args.tipo, args.recurso)
            print(f"{args.tipo} {args.recurso!r} desligado do rateio em {args.cenario!r}")
        elif args.acao == "ligar":
            vinculos.ligar_recurso(session, args.cenario, args.tipo, args.recurso)
            print(f"{args.tipo} {args.recurso!r} ligado ao rateio em {args.cenario!r}")
        elif args.acao == "status":
            rateio = vinculos.status_rateio_cenario(session, args.cenario)
            print(f"ambientes_desligados={sorted(rateio.ambientes_desligados)}")
            print(f"maquinas_desligadas={sorted(rateio.maquinas_desligadas)}")
            print(f"grupos_desligados={sorted(rateio.grupos_desligados)}")


def despesa_cmd(args: argparse.Namespace) -> None:
    with SessionLocal() as session:
        if args.acao == "aloca":
            despesa = session.query(Despesa).filter_by(codigo=args.despesa).one()
            alocacao = calcular_alocacao_despesa(session, despesa)
            produtos = {p.id: p for p in session.query(Produto).all()}
            if despesa.tipo != "fixa":
                print(f"Despesa {despesa.codigo} é variável ({despesa.percentual_receita:.2%} da receita): não é rateada")
                return
            print(f"Despesa {despesa.codigo} valor={despesa.valor:.4f} direcionador={despesa.direcionador.codigo}")
            for produto_id, valor in alocacao.items():
                produto = produtos[produto_id]
                print(f"  {produto.codigo:10s} {produto.nome:40.40s} valor_alocado={valor:.4f}")


def gasto_cmd(args: argparse.Namespace) -> None:
    with SessionLocal() as session:
        if args.acao == "add":
            linha = vinculos.adicionar_gasto(
                session, args.tipo, args.recurso, args.plano_de_contas, args.desembolso, args.retencao,
                atualizado_em=args.atualizado_em,
            )
            print(f"Gasto criado: id={linha.id} total={linha.total:.4f}")
        elif args.acao == "list":
            for linha in vinculos.listar_gastos(session, args.tipo, args.recurso):
                print(cadastro.formatar(linha))
        elif args.acao == "remove":
            arquivado = vinculos.remover_gasto(session, args.tipo, args.gasto_id, em=args.em)
            print(f"Gasto {args.tipo} id={args.gasto_id} removido" + (" (guardado no histórico)" if arquivado else ""))
        elif args.acao == "alterar":
            argumentos = {}
            if args.desembolso is not None:
                argumentos["desembolso"] = args.desembolso
            if args.retencao is not None:
                argumentos["retencao"] = args.retencao
            if args.plano_de_contas is not None:
                argumentos["plano_de_contas_codigo"] = args.plano_de_contas
            linha = vinculos.alterar_gasto(
                session, args.tipo, args.gasto_id, atualizado_em=args.atualizado_em, **argumentos
            )
            print(f"Gasto {args.tipo} id={linha.id} atualizado em {linha.atualizado_em:%Y-%m} total={linha.total:.4f}")


def _pedir_senha(args: argparse.Namespace) -> str:
    if args.senha:
        return args.senha
    senha = getpass.getpass("Senha: ")
    if senha != getpass.getpass("Repita a senha: "):
        raise ValueError("As senhas não conferem")
    return senha


def _usuario_por_email(session, email: str) -> Usuario:
    usuario = session.query(Usuario).filter_by(email=auth.normalizar_email(email)).one_or_none()
    if usuario is None:
        raise ValueError(f"Usuário {email!r} não encontrado")
    return usuario


def usuario_cmd(args: argparse.Namespace) -> None:
    with SessionLocal() as session:
        if args.acao == "criar":
            usuario = auth.criar_usuario(session, args.email, args.nome, _pedir_senha(args), admin=args.admin)
            session.commit()
            print(f"Usuário criado: {usuario.email} ({'admin' if usuario.admin else 'comum'})")
        elif args.acao == "listar":
            for u in session.query(Usuario).order_by(Usuario.nome).all():
                print(f"{u.email}	{u.nome}	{'admin' if u.admin else 'comum'}	{'ativo' if u.ativo else 'inativo'}")
        elif args.acao == "senha":
            usuario = _usuario_por_email(session, args.email)
            senha = _pedir_senha(args)
            auth.validar_senha(senha)
            usuario.senha_hash = auth.hash_senha(senha)
            session.commit()
            print(f"Senha de {usuario.email} redefinida (as sessões abertas dele foram encerradas).")
        elif args.acao in ("ativar", "desativar"):
            usuario = _usuario_por_email(session, args.email)
            usuario.ativo = args.acao == "ativar"
            session.commit()
            print(f"{usuario.email}: {'ativo' if usuario.ativo else 'inativo'}")


def planejamento_cmd(args: argparse.Namespace) -> None:
    with SessionLocal() as session:
        if args.acao == "volume-set":
            linha = planejamento.definir_volume_projetado(
                session, args.planejamento, args.cenario, args.produto, args.quantidade
            )
            print(f"Volume Projetado definido: id={linha.id} quantidade={linha.quantidade:.4f}")
        elif args.acao == "volume-list":
            produtos = {p.id: p for p in session.query(Produto).all()}
            for linha in planejamento.listar_volume_projetado(session, args.planejamento):
                produto = produtos[linha.produto_id]
                print(f"  cenario_id={linha.cenario_id} {produto.codigo:10s} quantidade={linha.quantidade:.4f}")
        elif args.acao == "resumo":
            print(f"\n=== Premissas do Planejamento {args.planejamento} ===")
            for tipo in ("comercial", "operacional", "despesas", "financeiro"):
                linhas = planejamento.listar_premissas(session, args.planejamento, tipo=tipo)
                print(f"\n--- {tipo.capitalize()} ---")
                if not linhas:
                    print("  (nenhuma)")
                for premissa in linhas:
                    print(f"  {premissa.codigo}: {premissa.texto}")


def _build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(prog="custos_precificacao")
    parser.add_argument(
        "--planejamento", dest="contexto_planejamento", default=None,
        help="Código do Planejamento em que trabalhar (Postgres; padrão: o do comando 'planejamento …', senão o oficial)",
    )
    subparsers = parser.add_subparsers(dest="comando", required=True)

    custeio_parser = subparsers.add_parser("custeio-run", help="Executa uma rodada de custeio")
    custeio_parser.add_argument("--cenario", required=True, help="Código do Cenário de Precificação")
    custeio_parser.add_argument("--observacao", default=None)
    custeio_parser.add_argument(
        "--competencia", default=None, help="Mês AAAA-MM cujas versões das entradas valem (padrão: mês atual)"
    )

    cadastro_parser = subparsers.add_parser("cadastro", help="Gerencia cadastros (CRUD genérico)")
    cadastro_sub = cadastro_parser.add_subparsers(dest="acao", required=True)

    p = cadastro_sub.add_parser("list", help="Lista todos os registros de um cadastro")
    p.add_argument("entidade")

    p = cadastro_sub.add_parser("get", help="Mostra um registro pela chave natural (ou id)")
    p.add_argument("entidade")
    p.add_argument("chave")

    p = cadastro_sub.add_parser("create", help="Cria um registro: campo=valor [campo=valor ...]")
    p.add_argument("entidade")
    p.add_argument("valores", nargs="*")

    p = cadastro_sub.add_parser("update", help="Atualiza um registro: campo=valor [campo=valor ...]")
    p.add_argument("entidade")
    p.add_argument("chave")
    p.add_argument("valores", nargs="*")
    p.add_argument(
        "--atualizado-em", default=None,
        help="Mês AAAA-MM da atualização de entradas de custo (preço de Insumo, horas, rendimento); padrão: mês atual",
    )

    p = cadastro_sub.add_parser("delete", help="Apaga um registro pela chave natural (ou id)")
    p.add_argument("entidade")
    p.add_argument("chave")

    roteiro_parser = subparsers.add_parser("roteiro", help="Gerencia o roteiro (BOM) de um produto")
    roteiro_sub = roteiro_parser.add_subparsers(dest="acao", required=True)

    for acao in ("add-maquina", "add-ambiente", "add-mao-de-obra"):
        p = roteiro_sub.add_parser(acao, help=f"Adiciona linha de {acao.removeprefix('add-')} ao roteiro")
        p.add_argument("produto", help="Código do produto")
        p.add_argument("atividade", help="Código da atividade")
        p.add_argument("recurso", help="Código do recurso (ou codigo_fopag para mão de obra)")
        p.add_argument("--tempo-horas", required=True)
        p.add_argument("--atualizado-em", default=None, help="Mês AAAA-MM a partir do qual a linha vale (padrão: mês atual)")

    p = roteiro_sub.add_parser("add-insumo", help="Adiciona linha de insumo ao roteiro")
    p.add_argument("produto", help="Código do produto")
    p.add_argument("atividade", help="Código da atividade")
    p.add_argument("--quantidade-kg", required=True)
    p.add_argument("--atualizado-em", default=None, help="Mês AAAA-MM a partir do qual a linha vale (padrão: mês atual)")
    grupo = p.add_mutually_exclusive_group(required=True)
    grupo.add_argument("--insumo", help="Código do Insumo comprado")
    grupo.add_argument("--insumo-produto", help="Código do produto/semiacabado usado como insumo")

    p = roteiro_sub.add_parser("list", help="Lista o roteiro de um produto")
    p.add_argument("produto", help="Código do produto")

    p = roteiro_sub.add_parser("remove", help="Remove uma linha de roteiro pelo id (a antiga fica no histórico)")
    p.add_argument("tipo", choices=list(vinculos.ROTEIRO_MODELS))
    p.add_argument("linha_id", type=int)
    p.add_argument("--em", default=None, help="Mês AAAA-MM a partir do qual a linha deixa de valer (padrão: mês atual)")

    p = roteiro_sub.add_parser("alterar", help="Altera o tempo (h) ou a quantidade (kg) de uma linha, com histórico")
    p.add_argument("tipo", choices=list(vinculos.ROTEIRO_MODELS))
    p.add_argument("linha_id", type=int)
    p.add_argument("--quantidade", required=True)
    p.add_argument("--atualizado-em", default=None, help="Mês AAAA-MM da atualização (padrão: mês atual)")

    cenario_parser = subparsers.add_parser("cenario", help="Gerencia o rateio ligado/desligado de um Cenário")
    cenario_sub = cenario_parser.add_subparsers(dest="acao", required=True)

    for acao in ("desligar", "ligar"):
        p = cenario_sub.add_parser(acao, help=f"{acao.capitalize()} um recurso do rateio nesse Cenário")
        p.add_argument("cenario", help="Código do Cenário de Precificação")
        p.add_argument("tipo", choices=list(vinculos.CENARIO_RECURSOS))
        p.add_argument("recurso", help="Código do recurso (ou codigo_fopag para funcionário)")

    p = cenario_sub.add_parser("status", help="Lista o que está desligado do rateio nesse Cenário")
    p.add_argument("cenario", help="Código do Cenário de Precificação")

    despesa_parser = subparsers.add_parser("despesa", help="Rateio de despesas por Direcionador")
    despesa_sub = despesa_parser.add_subparsers(dest="acao", required=True)

    p = despesa_sub.add_parser("aloca", help="Mostra a alocação de uma Despesa entre produtos (preview)")
    p.add_argument("despesa", help="Código da Despesa")

    gasto_parser = subparsers.add_parser("gasto", help="Gerencia as linhas de Gasto de um recurso")
    gasto_sub = gasto_parser.add_subparsers(dest="acao", required=True)

    p = gasto_sub.add_parser("add", help="Adiciona uma linha de Gasto")
    p.add_argument("tipo", choices=list(vinculos.GASTO_RECURSOS))
    p.add_argument("recurso", help="Código do recurso (ou codigo_fopag para funcionário)")
    p.add_argument("plano_de_contas", help="Código do Plano de Contas")
    p.add_argument("--desembolso", required=True)
    p.add_argument("--retencao", default="0")
    p.add_argument("--atualizado-em", default=None, help="Mês AAAA-MM a partir do qual o gasto vale (padrão: mês atual)")

    p = gasto_sub.add_parser("list", help="Lista os Gastos de um recurso")
    p.add_argument("tipo", choices=list(vinculos.GASTO_RECURSOS))
    p.add_argument("recurso", help="Código do recurso (ou codigo_fopag para funcionário)")

    p = gasto_sub.add_parser("remove", help="Encerra uma linha de Gasto pelo id (a antiga fica no histórico)")
    p.add_argument("tipo", choices=list(vinculos.GASTO_RECURSOS))
    p.add_argument("gasto_id", type=int)
    p.add_argument("--em", default=None, help="Mês AAAA-MM a partir do qual o gasto deixa de valer (padrão: mês atual)")

    p = gasto_sub.add_parser("alterar", help="Altera uma linha de Gasto, guardando a versão antiga no histórico")
    p.add_argument("tipo", choices=list(vinculos.GASTO_RECURSOS))
    p.add_argument("gasto_id", type=int)
    p.add_argument("--desembolso", default=None)
    p.add_argument("--retencao", default=None)
    p.add_argument("--plano-de-contas", default=None, help="Código do Plano de Contas")
    p.add_argument("--atualizado-em", default=None, help="Mês AAAA-MM da atualização (padrão: mês atual)")

    planejamento_parser = subparsers.add_parser(
        "planejamento", help="Volume Projetado e Premissas de um Planejamento"
    )
    planejamento_sub = planejamento_parser.add_subparsers(dest="acao", required=True)

    p = planejamento_sub.add_parser("volume-set", help="Define (cria ou atualiza) o Volume Projetado")
    p.add_argument("planejamento", help="Código do Planejamento")
    p.add_argument("cenario", help="Código do Cenário de Precificação")
    p.add_argument("produto", help="Código do Produto")
    p.add_argument("quantidade")

    p = planejamento_sub.add_parser("volume-list", help="Lista o Volume Projetado de um Planejamento")
    p.add_argument("planejamento", help="Código do Planejamento")

    p = planejamento_sub.add_parser("resumo", help="Premissas do Planejamento, nas 4 seções (Comercial/Operacional/Despesas/Financeiro)")
    p.add_argument("planejamento", help="Código do Planejamento")

    p = subparsers.add_parser("usuario", help="Gerencia usuários de login (e-mail + senha)")
    usuario_sub = p.add_subparsers(dest="acao", required=True)
    p = usuario_sub.add_parser("criar", help="Cria um usuário (pede a senha se não vier em --senha)")
    p.add_argument("email")
    p.add_argument("nome")
    p.add_argument("--admin", action="store_true", help="Pode gerenciar usuários")
    p.add_argument("--senha", default=None)
    usuario_sub.add_parser("listar", help="Lista os usuários")
    p = usuario_sub.add_parser("senha", help="Redefine a senha de um usuário")
    p.add_argument("email")
    p.add_argument("--senha", default=None)
    for acao in ("ativar", "desativar"):
        p = usuario_sub.add_parser(acao, help=f"{acao.capitalize()} um usuário")
        p.add_argument("email")

    return parser


def _schema_do_planejamento(codigo: str | None) -> dict[str, str]:
    from custos_precificacao import planejamento_schema as ps
    from custos_precificacao.db import engine

    if not ps.e_postgres(engine):
        return {}
    linha = ps.planejamento_por_codigo(engine, codigo) if codigo else ps.planejamento_padrao(engine)
    if linha is None:
        sys.exit(f"Erro: Planejamento {codigo!r} não encontrado" if codigo else "Erro: nenhum Planejamento cadastrado")
    if not linha.schema:
        sys.exit(f"Erro: Planejamento {linha.codigo!r} ainda não tem os dados separados (rode a inicialização)")
    return {"schema": linha.schema}


def main() -> None:
    parser = _build_parser()
    args = parser.parse_args()
    # autoria do histórico/custeio feitos pelo CLI; no Postgres, as sessões apontam para o schema do Planejamento
    SessionLocal.configure(info={"usuario": "terminal", **_schema_do_planejamento(args.contexto_planejamento or getattr(args, "planejamento", None))})

    try:
        if args.comando == "custeio-run":
            custeio_run(args.cenario, args.observacao, args.competencia)
        elif args.comando == "cadastro":
            cadastro_cmd(args)
        elif args.comando == "roteiro":
            roteiro_cmd(args)
        elif args.comando == "cenario":
            cenario_cmd(args)
        elif args.comando == "despesa":
            despesa_cmd(args)
        elif args.comando == "gasto":
            gasto_cmd(args)
        elif args.comando == "planejamento":
            planejamento_cmd(args)
        elif args.comando == "usuario":
            usuario_cmd(args)
    except ValueError as exc:
        print(f"Erro: {exc}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
