import { createClient as createSupabaseClient, type SupabaseClient, type User } from "@supabase/supabase-js";
import { canCompany, companyPermissions, getCompanyContext, type CompanyContext, type CompanyPermission } from "@/lib/company-context";
import { createAdminClient } from "@/lib/supabase-server";
import { ApiV1Error } from "@/lib/api-v1/errors";
import { assinaturaDaEmpresa } from "@/lib/fiscal/servidor/assinatura-da-empresa";

export type ApiV1AuthContext = {
  accessToken: string;
  supabase: SupabaseClient;
  admin: ReturnType<typeof createAdminClient>;
  user: User;
  companyContext: CompanyContext;
};

type JwtPayload = {
  exp?: number;
  sub?: string;
};

function getSupabaseEnv() {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
  const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

  if (!url || !anonKey) {
    throw new ApiV1Error(500, "supabase_not_configured", "Variaveis do Supabase nao configuradas.");
  }

  return { url, anonKey };
}

export function createExternalSupabaseClient(accessToken?: string) {
  const { url, anonKey } = getSupabaseEnv();
  return createSupabaseClient(url, anonKey, {
    auth: {
      autoRefreshToken: false,
      persistSession: false,
      detectSessionInUrl: false,
    },
    global: accessToken
      ? {
          headers: {
            Authorization: `Bearer ${accessToken}`,
          },
        }
      : undefined,
  });
}

export function extractBearerToken(request: Request) {
  const header = request.headers.get("authorization") || "";
  const [scheme, token] = header.split(" ");
  if (scheme?.toLowerCase() !== "bearer" || !token?.trim()) {
    throw new ApiV1Error(401, "missing_bearer_token", "Token Bearer obrigatorio.");
  }
  return token.trim();
}

function decodeBase64Url(value: string) {
  const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
  const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), "=");
  return Buffer.from(padded, "base64").toString("utf-8");
}

export function decodeJwtPayload(token: string): JwtPayload {
  const parts = token.split(".");
  if (parts.length < 2) {
    throw new ApiV1Error(401, "invalid_token", "Token JWT invalido.");
  }

  try {
    return JSON.parse(decodeBase64Url(parts[1])) as JwtPayload;
  } catch {
    throw new ApiV1Error(401, "invalid_token", "Token JWT invalido.");
  }
}

export function assertAccessTokenNotExpired(accessToken: string) {
  const payload = decodeJwtPayload(accessToken);
  if (typeof payload.exp === "number" && payload.exp <= Math.floor(Date.now() / 1000)) {
    throw new ApiV1Error(401, "token_expired", "Token expirado.");
  }
}

export async function getUserFromAccessToken(accessToken: string) {
  assertAccessTokenNotExpired(accessToken);
  const supabase = createExternalSupabaseClient(accessToken);
  const { data, error } = await supabase.auth.getUser(accessToken);

  if (error || !data.user) {
    throw new ApiV1Error(401, "invalid_token", "Token invalido ou expirado.");
  }

  return { supabase, user: data.user };
}

function validateRequestedCompanyScope(request: Request, companyContext: CompanyContext) {
  const requestedCompanyId =
    request.headers.get("x-ktalogu-company-id")?.trim() ||
    request.headers.get("x-company-id")?.trim() ||
    "";

  if (!requestedCompanyId) return;
  if (requestedCompanyId !== companyContext.companyId) {
    throw new ApiV1Error(403, "cross_company_access_denied", "O token autenticado nao possui acesso a empresa solicitada.");
  }
}

export async function requireApiV1Auth(request: Request, permission?: CompanyPermission): Promise<ApiV1AuthContext> {
  const accessToken = extractBearerToken(request);
  const { supabase, user } = await getUserFromAccessToken(accessToken);
  const companyContext = await getCompanyContext(supabase, user);
  validateRequestedCompanyScope(request, companyContext);

  if (permission && !canCompany(companyContext, user.id, permission)) {
    throw new ApiV1Error(403, "forbidden", "Sem permissao para acessar este recurso.");
  }

  /* A API externa nao passa pelo middleware: sem esta conferencia, empresa
     com assinatura vencida ou reembolsada seguia usando tudo por aqui. Mesma
     regra do painel: fecha quando ha registro e ele nao libera. */
  const admin = createAdminClient();
  if (companyContext.ownerId) {
    const assinatura = await assinaturaDaEmpresa(admin, companyContext.ownerId);
    if (!assinatura.ativa && assinatura.situacao !== "sem-registro") {
      throw new ApiV1Error(402, "subscription_inactive", "Assinatura do Ktalogu vencida ou inativa. Regularize em Assinatura para continuar.");
    }
  }

  return {
    accessToken,
    supabase,
    admin,
    user,
    companyContext,
  };
}

export async function buildSessionPayload(accessToken: string, refreshToken: string) {
  assertAccessTokenNotExpired(accessToken);
  const { user } = await getUserFromAccessToken(accessToken);
  const supabase = createExternalSupabaseClient(accessToken);
  const companyContext = await getCompanyContext(supabase, user);
  const payload = decodeJwtPayload(accessToken);

  return {
    access_token: accessToken,
    refresh_token: refreshToken,
    token_type: "bearer",
    expires_at: payload.exp || null,
    user: {
      id: user.id,
      email: user.email || null,
    },
    company: {
      id: companyContext.companyId,
      owner_id: companyContext.ownerId,
      tenant_ids: companyContext.tenantIds,
      permissions: companyPermissions.filter((permission) => canCompany(companyContext, user.id, permission)),
    },
  };
}
