import { betterAuth } from "better-auth";
import { magicLink } from "better-auth/plugins";
import { Kysely, PostgresDialect } from "kysely";
import nodemailer from "nodemailer";
import { Pool } from "pg";

import { recordDevMail } from "./dev-mailbox.mjs";

const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const authUrl = process.env.AUTH_URL || "http://localhost:3001";
const databaseUrl = process.env.DATABASE_URL || "postgres://productier:productier@localhost:5432/productier?sslmode=disable";
const authSecret = process.env.BETTER_AUTH_SECRET || "replace-me-with-a-long-random-secret";
const appMode = (process.env.APP_ENV || process.env.NODE_ENV || "development").trim().toLowerCase();
const productionLikeMode = appMode === "staging" || appMode === "production";
const magicLinkProvider = resolveMagicLinkProvider(process.env.AUTH_MAGIC_LINK_PROVIDER, productionLikeMode);
export const devMailboxEnabled = resolveDevMailboxEnabled(process.env.AUTH_DEV_MAILBOX_ENABLED, appMode);
const smtpConfig = magicLinkProvider === "smtp" ? loadSMTPConfig() : null;

validateRuntimeConfig();
const magicLinkTransport = createMagicLinkTransport();

const pool = new Pool({
  connectionString: databaseUrl
});

const db = new Kysely({
  dialect: new PostgresDialect({
    pool
  })
});

export const auth = betterAuth({
  appName: "Productier",
  baseURL: authUrl,
  secret: authSecret,
  trustedOrigins: [frontendUrl, authUrl],
  database: {
    db,
    type: "postgres"
  },
  emailAndPassword: {
    enabled: true,
    autoSignIn: true
  },
  user: {
    changeEmail: {
      enabled: false
    }
  },
  plugins: [
    magicLink({
      sendMagicLink: async ({ email, url }) => {
        await magicLinkTransport.send({
          email,
          url
        });
      }
    })
  ]
});

let authReadyPromise = null;

export async function ensureAuthReady() {
  if (!authReadyPromise) {
    authReadyPromise = auth.$context
      .then(async context => {
        await context.runMigrations();
        await magicLinkTransport.verify();
      })
      .catch(error => {
        authReadyPromise = null;
        throw error;
      });
  }

  return authReadyPromise;
}

export async function closeAuth() {
  await magicLinkTransport.close();
  await pool.end();
}

function validateRuntimeConfig() {
  assertHTTPURL(frontendUrl, "FRONTEND_URL");
  assertHTTPURL(authUrl, "AUTH_URL");
  assertAbsoluteURL(databaseUrl, "DATABASE_URL");

  if (!productionLikeMode) {
    return;
  }

  if (!process.env.DATABASE_URL) {
    throw new Error("DATABASE_URL must be explicitly set in staging/production");
  }
  if (isWeakSecret(authSecret)) {
    throw new Error("BETTER_AUTH_SECRET must be set to a strong non-placeholder value in staging/production");
  }
  assertSecurePublicURL(frontendUrl, "FRONTEND_URL");
  assertSecurePublicURL(authUrl, "AUTH_URL");
  if (magicLinkProvider !== "smtp") {
    throw new Error("AUTH_MAGIC_LINK_PROVIDER must be set to smtp in staging/production");
  }
  if (devMailboxEnabled) {
    throw new Error("AUTH_DEV_MAILBOX_ENABLED must be false in staging/production");
  }
}

function assertHTTPURL(value, envName) {
  let parsed;
  try {
    parsed = new URL(value);
  } catch (error) {
    throw new Error(`${envName} must be a valid absolute URL`);
  }
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
    throw new Error(`${envName} must use http or https`);
  }
}

function assertAbsoluteURL(value, envName) {
  try {
    // eslint-disable-next-line no-new
    new URL(value);
  } catch (error) {
    throw new Error(`${envName} must be a valid absolute URL`);
  }
}

function assertSecurePublicURL(value, envName) {
  const parsed = new URL(value);
  const hostname = parsed.hostname.toLowerCase();
  if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") {
    return;
  }
  if (parsed.protocol !== "https:") {
    throw new Error(`${envName} must use https in staging/production`);
  }
}

function isWeakSecret(secret) {
  const normalized = secret.trim().toLowerCase();
  if (
    normalized === "" ||
    normalized === "replace-me-with-a-long-random-secret" ||
    normalized === "changeme" ||
    normalized === "change-me" ||
    normalized === "replace-me"
  ) {
    return true;
  }
  return secret.trim().length < 16;
}

function resolveMagicLinkProvider(rawValue, isProductionLike) {
  const normalized = String(rawValue || "")
    .trim()
    .toLowerCase();
  if (!normalized) {
    return isProductionLike ? "smtp" : "dev-mailbox";
  }
  if (normalized !== "dev-mailbox" && normalized !== "smtp") {
    throw new Error("AUTH_MAGIC_LINK_PROVIDER must be one of: dev-mailbox, smtp");
  }
  return normalized;
}

function resolveDevMailboxEnabled(rawValue, mode) {
  const defaultEnabled = mode === "development" || mode === "test";
  if (rawValue === undefined || rawValue === null || String(rawValue).trim() === "") {
    return defaultEnabled;
  }
  return parseBoolean(rawValue, "AUTH_DEV_MAILBOX_ENABLED");
}

function parseBoolean(rawValue, envName) {
  const normalized = String(rawValue).trim().toLowerCase();
  switch (normalized) {
    case "1":
    case "true":
    case "yes":
    case "on":
      return true;
    case "0":
    case "false":
    case "no":
    case "off":
      return false;
    default:
      throw new Error(`${envName} must be a boolean value`);
  }
}

function loadSMTPConfig() {
  const host = String(process.env.AUTH_SMTP_HOST || "").trim();
  const username = String(process.env.AUTH_SMTP_USER || "").trim();
  const password = String(process.env.AUTH_SMTP_PASSWORD || "");
  const from = String(process.env.AUTH_MAIL_FROM || "").trim();

  if (!host) {
    throw new Error("AUTH_SMTP_HOST is required when AUTH_MAGIC_LINK_PROVIDER=smtp");
  }
  if (!username) {
    throw new Error("AUTH_SMTP_USER is required when AUTH_MAGIC_LINK_PROVIDER=smtp");
  }
  if (!password) {
    throw new Error("AUTH_SMTP_PASSWORD is required when AUTH_MAGIC_LINK_PROVIDER=smtp");
  }
  if (!from) {
    throw new Error("AUTH_MAIL_FROM is required when AUTH_MAGIC_LINK_PROVIDER=smtp");
  }

  const portRaw = String(process.env.AUTH_SMTP_PORT || "587").trim();
  const port = Number.parseInt(portRaw, 10);
  if (Number.isNaN(port) || port < 1 || port > 65535) {
    throw new Error("AUTH_SMTP_PORT must be a valid TCP port");
  }

  const secure = process.env.AUTH_SMTP_SECURE
    ? parseBoolean(process.env.AUTH_SMTP_SECURE, "AUTH_SMTP_SECURE")
    : port === 465;
  const skipVerify = process.env.AUTH_SMTP_SKIP_VERIFY
    ? parseBoolean(process.env.AUTH_SMTP_SKIP_VERIFY, "AUTH_SMTP_SKIP_VERIFY")
    : false;
  const tlsRejectUnauthorized = process.env.AUTH_SMTP_TLS_REJECT_UNAUTHORIZED
    ? parseBoolean(process.env.AUTH_SMTP_TLS_REJECT_UNAUTHORIZED, "AUTH_SMTP_TLS_REJECT_UNAUTHORIZED")
    : true;

  return {
    host,
    port,
    secure,
    username,
    password,
    from,
    skipVerify,
    tlsRejectUnauthorized
  };
}

function createMagicLinkTransport() {
  if (magicLinkProvider === "dev-mailbox") {
    return {
      async send({ email, url }) {
        await recordDevMail({
          kind: "magic-link",
          email,
          subject: "Your Productier magic link",
          url
        });
        console.log(`[Productier auth] magic link for ${email}: ${url}`);
      },
      async verify() {
        return;
      },
      async close() {
        return;
      }
    };
  }

  const transporter = nodemailer.createTransport({
    host: smtpConfig.host,
    port: smtpConfig.port,
    secure: smtpConfig.secure,
    auth: {
      user: smtpConfig.username,
      pass: smtpConfig.password
    },
    tls: {
      rejectUnauthorized: smtpConfig.tlsRejectUnauthorized
    }
  });

  return {
    async send({ email, url }) {
      const subject = "Your Productier magic link";
      const text = [
        "Sign in to Productier using this secure magic link:",
        url,
        "",
        "If you did not request this link, you can ignore this email."
      ].join("\n");
      const html = [
        "<p>Sign in to Productier using this secure magic link:</p>",
        `<p><a href="${url}">${url}</a></p>`,
        "<p>If you did not request this link, you can ignore this email.</p>"
      ].join("");

      await transporter.sendMail({
        from: smtpConfig.from,
        to: email,
        subject,
        text,
        html
      });
    },
    async verify() {
      if (smtpConfig.skipVerify) {
        return;
      }
      await transporter.verify();
    },
    async close() {
      if (typeof transporter.close === "function") {
        transporter.close();
      }
    }
  };
}
