import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
import { hashPassword, verifyPassword, generateToken, validarMatricula, validarSenha } from "./auth/utils";
import { registrarLogin } from "./logsLogin";
import { Id } from "./_generated/dataModel";

/**
 * Helper para verificar se usuário está bloqueado
 */
async function verificarBloqueioUsuario(ctx: any, usuarioId: Id<"usuarios">) {
  const bloqueio = await ctx.db
    .query("bloqueiosUsuarios")
    .withIndex("by_usuario", (q) => q.eq("usuarioId", usuarioId))
    .filter((q: any) => q.eq(q.field("ativo"), true))
    .first();

  return bloqueio !== null;
}

/**
 * Helper para verificar rate limiting por IP
 */
async function verificarRateLimitIP(ctx: any, ipAddress: string) {
  // Últimas 15 minutos
  const dataLimite = Date.now() - 15 * 60 * 1000;
  
  const tentativas = await ctx.db
    .query("logsLogin")
    .withIndex("by_ip", (q: any) => q.eq("ipAddress", ipAddress))
    .filter((q: any) => q.gte(q.field("timestamp"), dataLimite))
    .collect();

  const falhas = tentativas.filter((t: any) => !t.sucesso).length;
  
  // Bloquear se 5 ou mais tentativas falhas em 15 minutos
  return falhas >= 5;
}

/**
 * Login do usuário (aceita matrícula OU email)
 */
export const login = mutation({
  args: {
    matriculaOuEmail: v.string(), // Aceita matrícula ou email
    senha: v.string(),
    ipAddress: v.optional(v.string()),
    userAgent: v.optional(v.string()),
  },
  returns: v.union(
    v.object({
      sucesso: v.literal(true),
      token: v.string(),
      usuario: v.object({
        _id: v.id("usuarios"),
        matricula: v.string(),
        nome: v.string(),
        email: v.string(),
        role: v.object({
          _id: v.id("roles"),
          nome: v.string(),
          nivel: v.number(),
          setor: v.optional(v.string()),
        }),
        primeiroAcesso: v.boolean(),
      }),
    }),
    v.object({
      sucesso: v.literal(false),
      erro: v.string(),
    })
  ),
  handler: async (ctx, args) => {
    // Verificar rate limiting por IP
    if (args.ipAddress) {
      const ipBloqueado = await verificarRateLimitIP(ctx, args.ipAddress);
      if (ipBloqueado) {
        await registrarLogin(ctx, {
          matriculaOuEmail: args.matriculaOuEmail,
          sucesso: false,
          motivoFalha: "rate_limit_excedido",
          ipAddress: args.ipAddress,
          userAgent: args.userAgent,
        });

        return {
          sucesso: false as const,
          erro: "Muitas tentativas de login. Tente novamente em 15 minutos.",
        };
      }
    }

    // Determinar se é email ou matrícula
    const isEmail = args.matriculaOuEmail.includes("@");

    // Buscar usuário
    let usuario;
    if (isEmail) {
      usuario = await ctx.db
        .query("usuarios")
        .withIndex("by_email", (q) => q.eq("email", args.matriculaOuEmail))
        .first();
    } else {
      usuario = await ctx.db
        .query("usuarios")
        .withIndex("by_matricula", (q) => q.eq("matricula", args.matriculaOuEmail))
        .first();
    }

    if (!usuario) {
      await registrarLogin(ctx, {
        matriculaOuEmail: args.matriculaOuEmail,
        sucesso: false,
        motivoFalha: "usuario_inexistente",
        ipAddress: args.ipAddress,
        userAgent: args.userAgent,
      });

      return {
        sucesso: false as const,
        erro: "Credenciais incorretas.",
      };
    }

    // Verificar se usuário está bloqueado
    if (usuario.bloqueado || (await verificarBloqueioUsuario(ctx, usuario._id))) {
      await registrarLogin(ctx, {
        usuarioId: usuario._id,
        matriculaOuEmail: args.matriculaOuEmail,
        sucesso: false,
        motivoFalha: "usuario_bloqueado",
        ipAddress: args.ipAddress,
        userAgent: args.userAgent,
      });

      return {
        sucesso: false as const,
        erro: "Usuário bloqueado. Entre em contato com o TI.",
      };
    }

    // Verificar se usuário está ativo
    if (!usuario.ativo) {
      await registrarLogin(ctx, {
        usuarioId: usuario._id,
        matriculaOuEmail: args.matriculaOuEmail,
        sucesso: false,
        motivoFalha: "usuario_inativo",
        ipAddress: args.ipAddress,
        userAgent: args.userAgent,
      });

      return {
        sucesso: false as const,
        erro: "Usuário inativo. Entre em contato com o TI.",
      };
    }

    // Verificar tentativas de login (bloqueio temporário)
    const tentativasRecentes = usuario.tentativasLogin || 0;
    const ultimaTentativa = usuario.ultimaTentativaLogin || 0;
    const tempoDecorrido = Date.now() - ultimaTentativa;
    const TEMPO_BLOQUEIO = 30 * 60 * 1000; // 30 minutos

    // Se tentou 5 vezes e ainda não passou o tempo de bloqueio
    if (tentativasRecentes >= 5 && tempoDecorrido < TEMPO_BLOQUEIO) {
      await registrarLogin(ctx, {
        usuarioId: usuario._id,
        matriculaOuEmail: args.matriculaOuEmail,
        sucesso: false,
        motivoFalha: "bloqueio_temporario",
        ipAddress: args.ipAddress,
        userAgent: args.userAgent,
      });

      const minutosRestantes = Math.ceil((TEMPO_BLOQUEIO - tempoDecorrido) / 60000);
      return {
        sucesso: false as const,
        erro: `Conta temporariamente bloqueada. Tente novamente em ${minutosRestantes} minutos.`,
      };
    }

    // Resetar tentativas se passou o tempo de bloqueio
    if (tempoDecorrido > TEMPO_BLOQUEIO) {
      await ctx.db.patch(usuario._id, {
        tentativasLogin: 0,
        ultimaTentativaLogin: Date.now(),
      });
    }

    // Verificar senha
    const senhaValida = await verifyPassword(args.senha, usuario.senhaHash);

    if (!senhaValida) {
      // Incrementar tentativas
      const novasTentativas = tempoDecorrido > TEMPO_BLOQUEIO ? 1 : tentativasRecentes + 1;
      
      await ctx.db.patch(usuario._id, {
        tentativasLogin: novasTentativas,
        ultimaTentativaLogin: Date.now(),
      });

      await registrarLogin(ctx, {
        usuarioId: usuario._id,
        matriculaOuEmail: args.matriculaOuEmail,
        sucesso: false,
        motivoFalha: "senha_incorreta",
        ipAddress: args.ipAddress,
        userAgent: args.userAgent,
      });

      const tentativasRestantes = 5 - novasTentativas;
      if (tentativasRestantes > 0) {
        return {
          sucesso: false as const,
          erro: `Credenciais incorretas. ${tentativasRestantes} tentativas restantes.`,
        };
      } else {
        return {
          sucesso: false as const,
          erro: "Conta bloqueada por 30 minutos devido a múltiplas tentativas falhas.",
        };
      }
    }

    // Login bem-sucedido! Resetar tentativas
    await ctx.db.patch(usuario._id, {
      tentativasLogin: 0,
      ultimaTentativaLogin: undefined,
    });

    // Buscar role do usuário
    const role = await ctx.db.get(usuario.roleId);
    if (!role) {
      return {
        sucesso: false as const,
        erro: "Erro ao carregar permissões do usuário.",
      };
    }

    // Gerar token de sessão
    const token = generateToken();
    const agora = Date.now();
    const expiraEm = agora + 8 * 60 * 60 * 1000; // 8 horas

    // Criar sessão
    await ctx.db.insert("sessoes", {
      usuarioId: usuario._id,
      token,
      ipAddress: args.ipAddress,
      userAgent: args.userAgent,
      criadoEm: agora,
      expiraEm,
      ativo: true,
    });

    // Atualizar último acesso
    await ctx.db.patch(usuario._id, {
      ultimoAcesso: agora,
      atualizadoEm: agora,
    });

    // Log de login bem-sucedido
    await registrarLogin(ctx, {
      usuarioId: usuario._id,
      matriculaOuEmail: args.matriculaOuEmail,
      sucesso: true,
      ipAddress: args.ipAddress,
      userAgent: args.userAgent,
    });

    await ctx.db.insert("logsAcesso", {
      usuarioId: usuario._id,
      tipo: "login",
      ipAddress: args.ipAddress,
      userAgent: args.userAgent,
      detalhes: "Login realizado com sucesso",
      timestamp: agora,
    });

    return {
      sucesso: true as const,
      token,
      usuario: {
        _id: usuario._id,
        matricula: usuario.matricula,
        nome: usuario.nome,
        email: usuario.email,
        role: {
          _id: role._id,
          nome: role.nome,
          nivel: role.nivel,
          setor: role.setor,
        },
        primeiroAcesso: usuario.primeiroAcesso,
      },
    };
  },
});

/**
 * Logout do usuário
 */
export const logout = mutation({
  args: {
    token: v.string(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    // Buscar sessão
    const sessao = await ctx.db
      .query("sessoes")
      .withIndex("by_token", (q) => q.eq("token", args.token))
      .first();

    if (sessao) {
      // Desativar sessão
      await ctx.db.patch(sessao._id, {
        ativo: false,
      });

      // Log de logout
      await ctx.db.insert("logsAcesso", {
        usuarioId: sessao.usuarioId,
        tipo: "logout",
        timestamp: Date.now(),
      });
    }

    return null;
  },
});

/**
 * Verificar se token é válido e retornar usuário
 */
export const verificarSessao = query({
  args: {
    token: v.string(),
  },
  returns: v.union(
    v.object({
      valido: v.literal(true),
      usuario: v.object({
        _id: v.id("usuarios"),
        matricula: v.string(),
        nome: v.string(),
        email: v.string(),
        role: v.object({
          _id: v.id("roles"),
          nome: v.string(),
          nivel: v.number(),
          setor: v.optional(v.string()),
        }),
        primeiroAcesso: v.boolean(),
      }),
    }),
    v.object({
      valido: v.literal(false),
      motivo: v.optional(v.string()),
    })
  ),
  handler: async (ctx, args) => {
    // Buscar sessão
    const sessao = await ctx.db
      .query("sessoes")
      .withIndex("by_token", (q) => q.eq("token", args.token))
      .first();

    if (!sessao || !sessao.ativo) {
      return { valido: false as const, motivo: "Sessão não encontrada ou inativa" };
    }

    // Verificar se sessão expirou
    if (sessao.expiraEm < Date.now()) {
      // Não podemos fazer patch/insert em uma query
      // A expiração será tratada por uma mutation separada
      return { valido: false as const, motivo: "Sessão expirada" };
    }

    // Buscar usuário
    const usuario = await ctx.db.get(sessao.usuarioId);
    if (!usuario || !usuario.ativo) {
      return { valido: false as const, motivo: "Usuário não encontrado ou inativo" };
    }

    // Buscar role
    const role = await ctx.db.get(usuario.roleId);
    if (!role) {
      return { valido: false as const, motivo: "Role não encontrada" };
    }

    return {
      valido: true as const,
      usuario: {
        _id: usuario._id,
        matricula: usuario.matricula,
        nome: usuario.nome,
        email: usuario.email,
        role: {
          _id: role._id,
          nome: role.nome,
          nivel: role.nivel,
          setor: role.setor,
        },
        primeiroAcesso: usuario.primeiroAcesso,
      },
    };
  },
});

/**
 * Limpar sessões expiradas (chamada periodicamente)
 */
export const limparSessoesExpiradas = mutation({
  args: {},
  returns: v.object({
    sessoesLimpas: v.number(),
  }),
  handler: async (ctx) => {
    const agora = Date.now();
    const sessoes = await ctx.db
      .query("sessoes")
      .withIndex("by_ativo", (q) => q.eq("ativo", true))
      .collect();

    let sessoesLimpas = 0;

    for (const sessao of sessoes) {
      if (sessao.expiraEm < agora) {
        await ctx.db.patch(sessao._id, { ativo: false });
        
        await ctx.db.insert("logsAcesso", {
          usuarioId: sessao.usuarioId,
          tipo: "sessao_expirada",
          timestamp: agora,
        });

        sessoesLimpas++;
      }
    }

    return { sessoesLimpas };
  },
});

/**
 * Alterar senha (primeiro acesso ou reset)
 */
export const alterarSenha = mutation({
  args: {
    token: v.string(),
    senhaAtual: v.optional(v.string()),
    novaSenha: v.string(),
  },
  returns: v.union(
    v.object({ sucesso: v.literal(true) }),
    v.object({ sucesso: v.literal(false), erro: v.string() })
  ),
  handler: async (ctx, args) => {
    // Verificar sessão
    const sessao = await ctx.db
      .query("sessoes")
      .withIndex("by_token", (q) => q.eq("token", args.token))
      .first();

    if (!sessao || !sessao.ativo) {
      return { sucesso: false as const, erro: "Sessão inválida" };
    }

    const usuario = await ctx.db.get(sessao.usuarioId);
    if (!usuario) {
      return { sucesso: false as const, erro: "Usuário não encontrado" };
    }

    // Se não for primeiro acesso, verificar senha atual
    if (!usuario.primeiroAcesso && args.senhaAtual) {
      const senhaAtualValida = await verifyPassword(
        args.senhaAtual,
        usuario.senhaHash
      );
      if (!senhaAtualValida) {
        return { sucesso: false as const, erro: "Senha atual incorreta" };
      }
    }

    // Validar nova senha
    if (!validarSenha(args.novaSenha)) {
      return {
        sucesso: false as const,
        erro: "Senha deve ter no mínimo 8 caracteres, incluindo letras, números e símbolos",
      };
    }

    // Gerar hash da nova senha
    const novoHash = await hashPassword(args.novaSenha);

    // Atualizar senha
    await ctx.db.patch(usuario._id, {
      senhaHash: novoHash,
      primeiroAcesso: false,
      atualizadoEm: Date.now(),
    });

    // Log
    await ctx.db.insert("logsAcesso", {
      usuarioId: usuario._id,
      tipo: "senha_alterada",
      timestamp: Date.now(),
    });

    return { sucesso: true as const };
  },
});

