import { createServerClient } from '@supabase/ssr'
import { createClient as createSupabaseClient, type SupabaseClient } from '@supabase/supabase-js'
import { NextResponse, type NextRequest } from 'next/server'

import { lerVencimento, statusLibera } from '@/lib/assinatura'
import { acessosLembrados, type AcessoLembrado } from '@/lib/assinatura-memoria'

function isPublicPath(pathname: string) {
  return (
    pathname.startsWith('/api/auth/avatar') ||
    pathname.startsWith('/api/webhooks/') ||
    // O servidor MCP dos assistentes: quem chama e um programa, sem sessao de
    // navegador. A rota confere a chave de acesso em todo pedido.
    pathname === '/api/mcp' ||
    // A tela de permitir do login por botao dos assistentes. Quem chega aqui
    // pode nao estar logado: a propria tela tem o formulario de entrada, e
    // mandar a pessoa para o login comum perderia o pedido do assistente.
    pathname === '/oauth/authorize' ||
    // O clique em "Permitir" dessa tela. A rota confere a sessao, a origem do
    // pedido e a assinatura da empresa por conta propria, e responde com uma
    // recusa que a tela entende (em vez de um redirecionamento para o login).
    pathname === '/api/oauth/consentir' ||
    // Disparo dos avisos da agenda: quem chama e o agendador, que nao tem
    // sessao. A rota se protege com segredo no cabecalho. Caminho exato de
    // proposito — prefixo abriria as outras rotas de push junto.
    pathname === '/api/agenda/push/enviar' ||
    pathname === '/api/agenda/push/lembretes' ||
    // Relogio do atendimento: cutuca conversa parada e devolve o que ficou de
    // retomar. Mesmo agendador, mesmo segredo no cabecalho.
    pathname === '/api/chats/lembretes' ||
    // Volta extra da fila das notas fiscais, chamada por agendador externo.
    // Sem FISCAL_TAREFAS_SEGREDO a rota responde 404; com ele, exige o segredo.
    pathname === '/api/fiscal/tarefas' ||
    // A gravacao da live chega da loja (127.0.0.1), sem sessao de navegador.
    // A rota so aceita pedido assinado com a chave do servidor (2 minutos).
    pathname === '/api/live/gravacao' ||
    // "Quem e voce" da sala da live: sem sessao, so devolve para a sala sem nada.
    pathname === '/api/live/quem-sou' ||
    pathname.startsWith('/auth') ||
    pathname.startsWith('/reset-password') ||
    // Link curto dos arquivos do assistente: quem abre pode nao estar logado
    // (cliente no celular). A rota confere codigo de 12 letras e validade.
    pathname.startsWith('/baixar/') ||
    // Pagina de envio de imagem para o assistente, e a rota que recebe o
    // arquivo: quem abre e a pessoa no celular, sem login. As duas conferem o
    // codigo de 12 letras e a validade; a rota ainda limita tamanho e envios.
    pathname.startsWith('/enviar/') ||
    pathname.startsWith('/api/enviar/') ||
    pathname.startsWith('/pedido-compra/') ||
    // Relatorio da agenda compartilhado por link. Barra no fim de proposito:
    // exige o token no caminho, entao `/relatorio` sozinho continua fechado e
    // nao vira uma porta para listar o que existe.
    pathname.startsWith('/relatorio/') ||
    // Prova da arte para o cliente aprovar (KtaloguDesign): o token no caminho e a
    // unica credencial; as funcoes do banco so mostram a arte e so registram a resposta.
    pathname.startsWith('/prova/') ||
    // Link publico da arte (como o "qualquer pessoa com o link" do Canva): ver,
    // tocar e baixar. O codigo assinado no caminho e a unica credencial; a arte
    // vem do banco pelo codigo e nada se edita por aqui.
    pathname.startsWith('/arte/') ||
    pathname.startsWith('/api/artes/publica/') ||
    pathname.startsWith('/owner/acesso-manual') ||
    pathname.startsWith('/_next') ||
    pathname.startsWith('/static')
  )
}

function isSubscriptionPage(pathname: string) {
  return pathname === '/dashboard/assinatura' || pathname.startsWith('/dashboard/assinatura/')
}

function isAllowedBlockedApi(pathname: string) {
  return (
    pathname === '/api/company/users' ||
    pathname.startsWith('/api/company/users?') ||
    // O link de pagamento com a marca da empresa: e dele que a conta vencida renova.
    pathname === '/api/assinatura/link' ||
    pathname === '/api/assinatura/situacao' ||
    pathname.startsWith('/api/auth/avatar') ||
    pathname.startsWith('/api/webhooks/')
  )
}

/* A mesma leitura da tela e das rotas fiscais (`lib/assinatura`): linha sem
   status não é mais "ativa" por falta de informação. */
function isActiveSubscriptionStatus(rawStatus: unknown) {
  return statusLibera(typeof rawStatus === 'string' ? rawStatus : null).libera
}

function isExpiredAccess(rawExpiresAt: unknown) {
  const expiresAt = lerVencimento(rawExpiresAt)
  if (!expiresAt) return false
  return expiresAt.getTime() <= Date.now()
}

/*
  Memoria curta da assinatura.

  A checagem de `users_app` rodava em todo request (1,5 milhao de vezes),
  cada uma a 200 ms de ida ao banco. Agora vale por 5 minutos por e-mail,
  por instancia. O aviso de pagamento da Cakto apaga a lembranca do e-mail
  na hora (`lib/assinatura-memoria`). Quem esta bloqueado e lembrado so por
  30 segundos: se o pagamento entrar por outro caminho, a porta abre logo.
*/
const ASSINATURA_MS = 5 * 60 * 1000
const BLOQUEIO_MS = 30 * 1000
type Assinatura = AcessoLembrado
type LinhaDeAssinatura = { status?: unknown; expires_at?: unknown }
const assinaturas = acessosLembrados()

/* A chave de serviço só entra para ler a assinatura da EMPRESA de um
   funcionário: `users_app` deixa cada um ler a própria linha e mais nada. */
let servico: SupabaseClient | null = null
function clienteDeServico(): SupabaseClient | null {
  if (servico) return servico
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL
  const chave = String(process.env.SUPABASE_SERVICE_ROLE_KEY || '').trim()
  if (!url || !chave) return null
  servico = createSupabaseClient(url, chave, { auth: { autoRefreshToken: false, persistSession: false } })
  return servico
}

/*
  Funcionário não tem assinatura própria: vale a da empresa em que trabalha.

  Antes, quem não tinha linha em `users_app` passava — e funcionário nunca
  tem. Com a empresa vencida, o dono era barrado e o funcionário admin dele
  seguia comprando pacote e emitindo nota pela API. Agora o e-mail do
  funcionário leva ao e-mail de cobrança da empresa, e é essa linha que decide.
*/
async function assinaturaDaEmpresaDoFuncionario(userId: string, emailProprio: string): Promise<LinhaDeAssinatura | null> {
  const admin = clienteDeServico()
  if (!admin) return null
  try {
    const { data: vinculo } = await admin
      .from('company_users')
      .select('company_id,role,active')
      .eq('user_id', userId)
      .maybeSingle()
    if (!vinculo?.company_id || vinculo.role === 'owner' || vinculo.active === false) return null
    const { data: empresa } = await admin.from('companies').select('billing_email').eq('id', vinculo.company_id).maybeSingle()
    const cobranca = String(empresa?.billing_email ?? '').trim().toLowerCase()
    if (!cobranca || cobranca === emailProprio) return null
    const { data } = await admin.from('users_app').select('status, expires_at').ilike('email', cobranca).limit(1)
    return ((data ?? []) as LinhaDeAssinatura[])[0] ?? null
  } catch {
    /* Oscilação do banco não pode prender quem tem acesso: fica como estava. */
    return null
  }
}

async function lerAssinatura(
  supabase: ReturnType<typeof createServerClient>,
  email: string,
  userId: string
): Promise<Assinatura> {
  const chave = email.trim().toLowerCase()
  const lembrada = assinaturas.get(chave)
  if (lembrada && lembrada.ate > Date.now()) return lembrada
  const { data } = await supabase
    .from('users_app')
    .select('status, expires_at')
    .ilike('email', email)
    .maybeSingle()
  let linha = (data ?? null) as LinhaDeAssinatura | null
  if (!linha) linha = await assinaturaDaEmpresaDoFuncionario(userId, chave)
  const bloqueado = Boolean(linha) && (!isActiveSubscriptionStatus(linha?.status) || isExpiredAccess(linha?.expires_at))
  const valor: Assinatura = {
    existe: Boolean(linha),
    status: linha?.status,
    expires_at: linha?.expires_at,
    ate: Date.now() + (bloqueado ? BLOQUEIO_MS : ASSINATURA_MS),
  }
  if (assinaturas.size > 20000) assinaturas.clear()
  assinaturas.set(chave, valor)
  return valor
}

export async function updateSession(request: NextRequest) {
  const pathname = request.nextUrl.pathname
  const isApiV1Request = request.nextUrl.pathname.startsWith('/api/v1/');
  const isApiRequest = pathname.startsWith('/api/')
  let supabaseResponse = NextResponse.next({
    request,
  })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value))
          supabaseResponse = NextResponse.next({
            request,
          })
          cookiesToSet.forEach(({ name, value, options }) =>
            supabaseResponse.cookies.set(name, value, options)
          )
        },
      },
    }
  )

  // IMPORTANT: Avoid writing any logic between createServerClient and
  // supabase.auth.getClaims(). A simple mistake could make it very hard to
  // debug issues with users being randomly logged out.

  /* `getClaims` confere a assinatura do token aqui mesmo, com a chave
     publica do projeto (ES256), e renova a sessao quando ela venceu. O
     `getUser` de antes ia ao Supabase Auth em TODO request: 144 mil idas por
     dia, 200 ms cada, antes de qualquer resposta. */
  const { data: claimsData } = await supabase.auth.getClaims()
  const claims = claimsData?.claims
  const user =
    claims && typeof claims.sub === 'string' && claims.sub
      ? { id: claims.sub, email: typeof claims.email === 'string' ? claims.email : '' }
      : null

  if (user?.email && !isApiV1Request && !isPublicPath(pathname)) {
    const accessRow = await lerAssinatura(supabase, user.email, user.id)

    const accessBlocked =
      accessRow.existe &&
      (!isActiveSubscriptionStatus(accessRow.status) || isExpiredAccess(accessRow.expires_at))

    if (accessBlocked) {
      if (isApiRequest && !isAllowedBlockedApi(pathname)) {
        return NextResponse.json(
          { error: 'Acesso bloqueado. Regularize a assinatura para continuar.' },
          { status: 402 }
        )
      }

      if (!isSubscriptionPage(pathname)) {
        const url = request.nextUrl.clone()
        url.pathname = '/dashboard/assinatura'
        url.search = ''
        return NextResponse.redirect(url)
      }
    }
  }

  // Se o usuário NÃO estiver logado e tentar acessar rotas protegidas
  if (
    !user &&
    !isApiV1Request &&
    !isPublicPath(pathname)
  ) {
    // Se não for a página de login, redireciona para login
    if (pathname !== '/') {
        const url = request.nextUrl.clone()
        url.pathname = '/'
        return NextResponse.redirect(url)
    }
  }

  // Se o usuário ESTIVER logado e tentar acessar a página de login (/),
  // redireciona para o dashboard
  if (user && pathname === '/') {
    const url = request.nextUrl.clone()
    url.pathname = '/dashboard'
    return NextResponse.redirect(url)
  }

  return supabaseResponse
}
