import "server-only";
import { createHash, randomBytes } from "node:crypto";
import { Prisma } from "@prisma/client";

import { AuthTokenType } from "@prisma/client";

import { prisma } from "@/lib/prisma";

const AUTH_TOKEN_SECRET = process.env.AUTH_SECRET;
const FALLBACK_AUTH_TOKEN_SECRET = "whatsapp-inbox-dev-secret";
const RESOLVED_SECRET = AUTH_TOKEN_SECRET || (process.env.NODE_ENV === "production" ? undefined : FALLBACK_AUTH_TOKEN_SECRET);

if (!RESOLVED_SECRET) {
  throw new Error("AUTH_SECRET is required in production for secure token generation.");
}

const textEncoder = new TextEncoder();
const tokenSecretBytes = textEncoder.encode(RESOLVED_SECRET);

const RESET_DEFAULT_TTL_MINUTES = 60;
const INVITE_DEFAULT_TTL_MINUTES = 72 * 60;

type HashInput = {
  token: string;
};

type TokenResult = {
  id: string;
  userId: string;
  tenantId: string;
  tokenType: AuthTokenType;
  tokenHash: string;
  expiresAt: Date;
  consumedAt: Date | null;
  createdAt: Date;
  metadataJson: string | null;
};

type CreateAuthTokenInput = {
  userId: string;
  tenantId: string;
  tokenType: AuthTokenType;
  ttlMinutes?: number;
  createdByUserId?: string | null;
  metadata?: unknown;
};

type ResolveResult =
  | {
      valid: true;
      token: TokenResult;
    }
  | {
      valid: false;
      reason: "missing" | "expired" | "consumed";
    };

function normalizeTokenMetadata(metadata: unknown) {
  if (metadata === undefined) {
    return null;
  }

  try {
    return JSON.stringify(metadata);
  } catch {
    return null;
  }
}

function serializePayload(input: HashInput) {
  const bytes = new TextEncoder().encode(`${input.token}`);
  const payload = new Uint8Array(tokenSecretBytes.length + bytes.length);
  payload.set(tokenSecretBytes, 0);
  payload.set(bytes, tokenSecretBytes.length);
  return payload;
}

function hashToken(token: string) {
  if (!token) {
    return "";
  }

  const digest = createHash("sha256")
    .update(serializePayload({ token }))
    .digest();
  return digest.toString("base64url");
}

function newToken() {
  return randomBytes(32).toString("base64url");
}

function resolveTokenTypeTTL(type: AuthTokenType) {
  if (type === AuthTokenType.INVITE_ACCEPTANCE) {
    return INVITE_DEFAULT_TTL_MINUTES;
  }

  return RESET_DEFAULT_TTL_MINUTES;
}

export function makeResetUrl(rawToken: string) {
  const baseUrl = (process.env.APP_URL || process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000").replace(/\/+$/, "");
  return `${baseUrl}/reset-password?token=${encodeURIComponent(rawToken)}`;
}

export function makeInviteUrl(rawToken: string) {
  const baseUrl = (process.env.APP_URL || process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000").replace(/\/+$/, "");
  return `${baseUrl}/invite/${encodeURIComponent(rawToken)}`;
}

export async function createAuthToken(input: CreateAuthTokenInput) {
  const ttl = resolveTokenTypeTTL(input.tokenType);
  const rawToken = newToken();
  const tokenHash = hashToken(rawToken);

  const now = new Date();
  const expiresAt = new Date(now.getTime() + ttl * 60 * 1000);

  await prisma.authToken.deleteMany({
    where: {
      userId: input.userId,
      tokenType: input.tokenType,
      consumedAt: null
    }
  });

  const token = await prisma.authToken.create({
    data: {
      userId: input.userId,
      tenantId: input.tenantId,
      tokenType: input.tokenType,
      tokenHash,
      expiresAt,
      createdByUser: input.createdByUserId,
      metadataJson: normalizeTokenMetadata(input.metadata)
    }
  });

  return {
    rawToken,
    tokenId: token.id,
    expiresAt: token.expiresAt,
    ttlMinutes: ttl
  };
}

export async function consumeAuthToken(raw: string, tokenType: AuthTokenType): Promise<ResolveResult> {
  if (!raw) {
    return { valid: false, reason: "missing" };
  }

  const tokenHash = hashToken(raw.trim());
  const now = new Date();

  const token = await prisma.authToken.findFirst({
    where: {
      tokenType,
      tokenHash,
      consumedAt: null
    },
    select: {
      id: true,
      userId: true,
      tenantId: true,
      tokenType: true,
      tokenHash: true,
      expiresAt: true,
      consumedAt: true,
      createdAt: true,
      metadataJson: true
    } satisfies Prisma.AuthTokenSelect
  });

  if (!token) {
    return { valid: false, reason: "missing" };
  }

  if (token.expiresAt <= now) {
    return { valid: false, reason: "expired" };
  }

  if (token.consumedAt) {
    return { valid: false, reason: "consumed" };
  }

  return { valid: true, token };
}

export async function markAuthTokenConsumed(tokenId: string, now: Date) {
  await prisma.authToken.update({
    where: { id: tokenId },
    data: { consumedAt: now }
  });
}

export async function revokeUserAuthTokens(userId: string, tokenType: AuthTokenType) {
  await prisma.authToken.deleteMany({
    where: { userId, tokenType }
  });
}
