import nodemailer from "nodemailer";
import { prisma } from "@/lib/prisma";
import type { MailSetting, SmsSetting, WhatsappSetting } from "@prisma/client";

/* OTP delivery channels. Credentials live in two places:
   - each institution configures its own SMTP / SMS gateway from the
     institution Control Panel (scope INSTITUTION),
   - the platform-level fallback is configured from the central panel
     (scope CENTRAL) and also serves central (super-admin) accounts.
   Institutions without an active row of their own fall back to CENTRAL. */

export type MailSettingView = Omit<MailSetting, "password"> & { hasPassword: boolean };
export type SmsSettingView = Omit<SmsSetting, "authPassword"> & { hasPassword: boolean };
export type WhatsappSettingView = Omit<WhatsappSetting, "authToken"> & { hasToken: boolean };

export function toMailView(s: MailSetting | null): MailSettingView | null {
  if (!s) return null;
  const { password, ...rest } = s;
  return { ...rest, hasPassword: Boolean(password) };
}

export function toSmsView(s: SmsSetting | null): SmsSettingView | null {
  if (!s) return null;
  const { authPassword, ...rest } = s;
  return { ...rest, hasPassword: Boolean(authPassword) };
}

export function toWhatsappView(s: WhatsappSetting | null): WhatsappSettingView | null {
  if (!s) return null;
  const { authToken, ...rest } = s;
  return { ...rest, hasToken: Boolean(authToken) };
}

/** Active mail setting for an institution, else the active central row. */
export async function resolveMailSetting(institutionId: number | null) {
  if (institutionId) {
    const own = await prisma.mailSetting.findFirst({
      where: { institutionId, active: true },
      orderBy: { id: "asc" },
    });
    if (own) return { setting: own, source: "INSTITUTION" as const };
  }
  const central = await prisma.mailSetting.findFirst({
    where: { scope: "CENTRAL", institutionId: null, active: true },
    orderBy: { id: "asc" },
  });
  if (central) return { setting: central, source: "CENTRAL" as const };
  // deployment-level central credential (SMTP_* in the environment) —
  // the platform mail sender when no CENTRAL row has been configured in
  // the database. Port 587 uses STARTTLS, 465 implicit TLS (sendMail).
  if (process.env.SMTP_HOST) {
    const fallback = {
      id: 0,
      scope: "CENTRAL",
      institutionId: null,
      host: process.env.SMTP_HOST,
      port: Number(process.env.SMTP_PORT || 587) || 587,
      secure: true,
      username: process.env.SMTP_USER ?? "",
      password: process.env.SMTP_PASS ?? "",
      fromEmail: process.env.FROM_EMAIL || process.env.SMTP_USER || "",
      fromName: "IncrelogEMS",
      active: true,
      createdAt: new Date(),
      updatedAt: new Date(),
    } as MailSetting;
    return { setting: fallback, source: "CENTRAL" as const };
  }
  return null;
}

/** Active SMS setting for an institution, else the active central row. */
export async function resolveSmsSetting(institutionId: number | null) {
  if (institutionId) {
    const own = await prisma.smsSetting.findFirst({
      where: { institutionId, active: true },
      orderBy: { id: "asc" },
    });
    if (own) return { setting: own, source: "INSTITUTION" as const };
  }
  const central = await prisma.smsSetting.findFirst({
    where: { scope: "CENTRAL", institutionId: null, active: true },
    orderBy: { id: "asc" },
  });
  return central ? { setting: central, source: "CENTRAL" as const } : null;
}

/** Active WhatsApp setting for an institution, else the active central row. */
export async function resolveWhatsappSetting(institutionId: number | null) {
  if (institutionId) {
    const own = await prisma.whatsappSetting.findFirst({
      where: { institutionId, active: true },
      orderBy: { id: "asc" },
    });
    if (own) return { setting: own, source: "INSTITUTION" as const };
  }
  const central = await prisma.whatsappSetting.findFirst({
    where: { scope: "CENTRAL", institutionId: null, active: true },
    orderBy: { id: "asc" },
  });
  return central ? { setting: central, source: "CENTRAL" as const } : null;
}

export interface VerificationToggles {
  result: boolean;
  examSchedule: boolean;
  inventory: boolean;
}

/**
 * Which documents may carry verification QR codes — institution row first,
 * active central row as the fallback, everything-on when neither exists.
 * Toggles gate ISSUING (minting new codes), never the public verification
 * of codes that were already printed.
 */
export async function resolveVerificationSetting(institutionId: number | null): Promise<{
  toggles: VerificationToggles;
  source: "INSTITUTION" | "CENTRAL" | "DEFAULT";
}> {
  if (institutionId) {
    const own = await prisma.verificationSetting.findFirst({
      where: { institutionId, active: true },
      orderBy: { id: "asc" },
    });
    if (own) {
      return {
        toggles: { result: own.result, examSchedule: own.examSchedule, inventory: own.inventory },
        source: "INSTITUTION",
      };
    }
  }
  const central = await prisma.verificationSetting.findFirst({
    where: { scope: "CENTRAL", institutionId: null, active: true },
    orderBy: { id: "asc" },
  });
  if (central) {
    return {
      toggles: { result: central.result, examSchedule: central.examSchedule, inventory: central.inventory },
      source: "CENTRAL",
    };
  }
  return { toggles: { result: true, examSchedule: true, inventory: true }, source: "DEFAULT" };
}

export async function sendMail(
  setting: MailSetting,
  opts: { to: string; subject: string; text: string; html?: string },
): Promise<void> {
  const transport = nodemailer.createTransport({
    host: setting.host,
    port: setting.port,
    secure: setting.secure && setting.port === 465,
    requireTLS: setting.secure && setting.port !== 465,
    auth: setting.username ? { user: setting.username, pass: setting.password } : undefined,
    connectionTimeout: 10_000,
  });
  try {
    await transport.sendMail({
      from: `"${setting.fromName || "Increlog EMS"}" <${setting.fromEmail}>`,
      to: opts.to,
      subject: opts.subject,
      text: opts.text,
      html: opts.html,
    });
  } finally {
    transport.close();
  }
}

/** Parse "Key: Value" lines into fetch headers. */
function parseHeaders(raw: string | null): Record<string, string> {
  const headers: Record<string, string> = {};
  for (const line of (raw ?? "").split(/\r?\n/)) {
    const idx = line.indexOf(":");
    if (idx > 0) headers[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
  }
  return headers;
}

/**
 * Generic SMS gateway call — works with the simple HTTP APIs most bulk-SMS
 * providers expose. Placeholders {phone} {message} {sender} are replaced in
 * the URL (GET) or in the body template (POST). If authUsername is set,
 * basic-auth is appended.
 */
export async function sendSms(
  setting: SmsSetting,
  opts: { phone: string; message: string },
): Promise<void> {
  const vars: Record<string, string> = {
    phone: encodeURIComponent(opts.phone),
    message: encodeURIComponent(opts.message),
    sender: setting.senderId ?? "",
  };
  const fill = (s: string) =>
    s
      .replace(/\{phone\}/g, vars.phone)
      .replace(/\{message\}/g, vars.message)
      .replace(/\{sender\}/g, vars.sender);

  const method = setting.method === "GET" ? "GET" : "POST";
  const url = fill(setting.apiUrl);
  const headers: Record<string, string> = {
    Accept: "application/json, text/plain, */*",
    ...parseHeaders(setting.headersRaw),
  };
  if (setting.authUsername) {
    headers["Authorization"] =
      "Basic " + Buffer.from(`${setting.authUsername}:${setting.authPassword ?? ""}`).toString("base64");
  }

  let body: string | undefined;
  if (method === "POST") {
    body = setting.bodyTemplate ? fill(setting.bodyTemplate) : fill("phone={phone}&message={message}");
    if (!headers["Content-Type"]) headers["Content-Type"] = "application/x-www-form-urlencoded";
  }

  const res = await fetch(url, { method, headers, body, signal: AbortSignal.timeout(10_000) });
  if (!res.ok) throw new Error(`SMS gateway HTTP ${res.status}`);
}

/**
 * Generic WhatsApp gateway call — same shape as sendSms so it covers the
 * Meta Cloud API (graph.facebook.com + {phone}/{message} JSON body),
 * Twilio or any local BSP. If authToken is set, a Bearer header is added.
 */
export async function sendWhatsapp(
  setting: WhatsappSetting,
  opts: { phone: string; message: string },
): Promise<void> {
  const vars: Record<string, string> = {
    phone: encodeURIComponent(opts.phone),
    message: encodeURIComponent(opts.message),
    sender: setting.senderId ?? "",
  };
  const fill = (s: string) =>
    s
      .replace(/\{phone\}/g, vars.phone)
      .replace(/\{message\}/g, vars.message)
      .replace(/\{sender\}/g, vars.sender);

  const method = setting.method === "GET" ? "GET" : "POST";
  const url = fill(setting.apiUrl);
  const headers: Record<string, string> = {
    Accept: "application/json, text/plain, */*",
    ...parseHeaders(setting.headersRaw),
  };
  if (setting.authToken) headers["Authorization"] = `Bearer ${setting.authToken}`;

  let body: string | undefined;
  if (method === "POST") {
    body = setting.bodyTemplate ? fill(setting.bodyTemplate) : fill("phone={phone}&message={message}");
    if (!headers["Content-Type"]) {
      headers["Content-Type"] = body.trimStart().startsWith("{")
        ? "application/json"
        : "application/x-www-form-urlencoded";
    }
  }

  const res = await fetch(url, { method, headers, body, signal: AbortSignal.timeout(10_000) });
  if (!res.ok) throw new Error(`WhatsApp gateway HTTP ${res.status}`);
}

/** j***@provider.com */
export function maskEmail(email: string): string {
  const [name, domain] = email.split("@");
  if (!domain) return email;
  const visible = name.slice(0, 1);
  return `${visible}${"*".repeat(Math.max(2, Math.min(6, name.length - 1)))}@${domain}`;
}

/** +88017******23 style — last 2 digits stay visible. */
export function maskPhone(phone: string): string {
  const digits = phone.replace(/[^\d+]/g, "");
  if (digits.length < 6) return "*".repeat(Math.max(3, digits.length));
  return `${digits.slice(0, digits.length - 2).replace(/\d/g, "*")}${digits.slice(-2)}`;
}
