import { createHash, randomBytes } from "node:crypto";
import type { ResultSetHeader, RowDataPacket } from "mysql2/promise";
import { ensureOfficialMailSchema, getDbPool } from "@/lib/db";
import { hasActiveGrowthPlanByOwnerEmail } from "@/lib/mail-billing-access";

const API_KEY_PREFIX = "om_live_";
const MAX_ACTIVE_API_KEYS = 10;

type MailboxAccountRow = RowDataPacket & {
  mailbox_email: string;
  mailbox_id: number;
  mailbox_status: "active" | "disabled" | "pending_dns";
  owner_email: string;
  owner_user_id: number;
  user_id: number;
};

type ApiKeyRow = RowDataPacket & {
  created_at: Date;
  id: number;
  key_last_four: string;
  key_prefix: string;
  last_used_at: Date | null;
  name: string;
};

type AuthenticatedApiKeyRow = RowDataPacket & {
  api_key_id: number;
  mailbox_email: string;
  owner_email: string;
  owner_user_id: number;
};

type DeveloperMessageStatusRow = RowDataPacket & {
  created_at: Date;
  from_address: string;
  id: number;
  message_id_header: string | null;
  subject: string;
  to_addresses: string;
  transport_response: string | null;
  transport_status: "failed" | "queued" | "saved" | "sent";
  updated_at: Date;
};

type DeveloperMessageDeliveryLogRow = RowDataPacket & {
  created_at: Date;
  id: number;
  message_id_header: string | null;
  response_text: string | null;
  status: "failed" | "queued" | "saved" | "sent";
  target_address: string;
  transport: string;
};

export type MailDeveloperApiKey = {
  createdAt: string;
  displayKey: string;
  id: number;
  lastUsedAt: string | null;
  name: string;
};

export type MailDeveloperMessageStatus = {
  createdAt: string;
  from: string;
  logs: Array<{
    createdAt: string;
    id: number;
    messageIdHeader: string | null;
    response: string | null;
    status: "failed" | "queued" | "saved" | "sent";
    target: string;
    transport: string;
  }>;
  messageId: number;
  messageIdHeader: string | null;
  response: string | null;
  status: "failed" | "queued" | "saved" | "sent";
  subject: string;
  to: string;
  updatedAt: string;
};

function normalizeAccountEmail(value: string) {
  return value.trim().toLowerCase();
}

function hashApiKey(value: string) {
  return createHash("sha256").update(value, "utf8").digest("hex");
}

function normalizeKeyName(value: string) {
  return value.replace(/\s+/g, " ").trim().slice(0, 80);
}

async function getMailboxAccountByEmail(email: string) {
  await ensureOfficialMailSchema();
  const [rows] = await getDbPool().query<MailboxAccountRow[]>(
    `
      SELECT
        u.id AS user_id,
        m.id AS mailbox_id,
        m.email AS mailbox_email,
        m.status AS mailbox_status,
        COALESCE(mtm.owner_user_id, u.id) AS owner_user_id,
        COALESCE(owner.email, u.email) AS owner_email
      FROM users u
      INNER JOIN mailboxes m ON m.user_id = u.id
      LEFT JOIN managed_team_mailboxes mtm
        ON mtm.email = u.email
       AND mtm.status = 'active'
      LEFT JOIN users owner ON owner.id = mtm.owner_user_id
      WHERE u.email = ?
      ORDER BY m.id DESC
      LIMIT 1
    `,
    [normalizeAccountEmail(email)],
  );

  return rows[0] ?? null;
}

async function hasGrowthPlan(input: { owner_email: string }) {
  return hasActiveGrowthPlanByOwnerEmail(input.owner_email);
}

function toApiKey(row: ApiKeyRow): MailDeveloperApiKey {
  return {
    createdAt: row.created_at.toISOString(),
    displayKey: `${row.key_prefix}...${row.key_last_four}`,
    id: Number(row.id),
    lastUsedAt: row.last_used_at?.toISOString() ?? null,
    name: row.name,
  };
}

export async function listMailboxDeveloperApiKeys(email: string) {
  const account = await getMailboxAccountByEmail(email);

  if (!account) {
    return {
      keys: [] satisfies MailDeveloperApiKey[],
      mailbox: normalizeAccountEmail(email),
      ready: false,
      planAvailable: false,
    };
  }

  const planAvailable = await hasGrowthPlan(account);

  const [rows] = await getDbPool().query<ApiKeyRow[]>(
    `
      SELECT id, name, key_prefix, key_last_four, last_used_at, created_at
      FROM mailbox_developer_api_keys
      WHERE owner_user_id = ?
        AND mailbox_id = ?
        AND status = 'active'
      ORDER BY created_at DESC, id DESC
    `,
    [account.user_id, account.mailbox_id],
  );

  return {
    keys: rows.map(toApiKey),
    mailbox: account.mailbox_email,
    ready: account.mailbox_status === "active",
    planAvailable,
  };
}

export async function createMailboxDeveloperApiKey(email: string, name: string) {
  const account = await getMailboxAccountByEmail(email);

  if (!account || account.mailbox_status !== "active") {
    throw new Error("mailbox-not-ready");
  }

  if (!(await hasGrowthPlan(account))) {
    throw new Error("growth-plan-required");
  }

  const normalizedName = normalizeKeyName(name);

  if (!normalizedName) {
    throw new Error("api-key-name-required");
  }

  const [countRows] = await getDbPool().query<(RowDataPacket & { total: number })[]>(
    `
      SELECT COUNT(*) AS total
      FROM mailbox_developer_api_keys
      WHERE owner_user_id = ?
        AND mailbox_id = ?
        AND status = 'active'
    `,
    [account.user_id, account.mailbox_id],
  );

  if (Number(countRows[0]?.total ?? 0) >= MAX_ACTIVE_API_KEYS) {
    throw new Error("api-key-limit-reached");
  }

  const secret = `${API_KEY_PREFIX}${randomBytes(32).toString("base64url")}`;
  const keyPrefix = secret.slice(0, 16);
  const keyLastFour = secret.slice(-4);
  const [result] = await getDbPool().query<ResultSetHeader>(
    `
      INSERT INTO mailbox_developer_api_keys (
        owner_user_id,
        mailbox_id,
        name,
        key_prefix,
        key_last_four,
        key_hash
      ) VALUES (?, ?, ?, ?, ?, ?)
    `,
    [
      account.user_id,
      account.mailbox_id,
      normalizedName,
      keyPrefix,
      keyLastFour,
      hashApiKey(secret),
    ],
  );

  return {
    key: {
      createdAt: new Date().toISOString(),
      displayKey: `${keyPrefix}...${keyLastFour}`,
      id: Number(result.insertId),
      lastUsedAt: null,
      name: normalizedName,
    } satisfies MailDeveloperApiKey,
    secret,
  };
}

export async function revokeMailboxDeveloperApiKey(email: string, apiKeyId: number) {
  const account = await getMailboxAccountByEmail(email);

  if (!account || !Number.isInteger(apiKeyId) || apiKeyId <= 0) {
    throw new Error("api-key-not-found");
  }

  const [result] = await getDbPool().query<ResultSetHeader>(
    `
      UPDATE mailbox_developer_api_keys
      SET status = 'revoked', revoked_at = NOW(), updated_at = NOW()
      WHERE id = ?
        AND owner_user_id = ?
        AND mailbox_id = ?
        AND status = 'active'
    `,
    [apiKeyId, account.user_id, account.mailbox_id],
  );

  if (result.affectedRows === 0) {
    throw new Error("api-key-not-found");
  }
}

export async function authenticateMailboxDeveloperApiKey(secret: string) {
  const normalizedSecret = secret.trim();

  if (!normalizedSecret.startsWith(API_KEY_PREFIX) || normalizedSecret.length < 40) {
    return null;
  }

  await ensureOfficialMailSchema();
  const [rows] = await getDbPool().query<AuthenticatedApiKeyRow[]>(
    `
      SELECT
        api_key.id AS api_key_id,
        mailbox.email AS mailbox_email,
        COALESCE(team.owner_user_id, owner.id) AS owner_user_id,
        COALESCE(team_owner.email, owner.email) AS owner_email
      FROM mailbox_developer_api_keys api_key
      INNER JOIN users owner ON owner.id = api_key.owner_user_id
      INNER JOIN mailboxes mailbox ON mailbox.id = api_key.mailbox_id
      LEFT JOIN managed_team_mailboxes team
        ON team.email = owner.email
       AND team.status = 'active'
      LEFT JOIN users team_owner ON team_owner.id = team.owner_user_id
      WHERE api_key.key_hash = ?
        AND api_key.status = 'active'
        AND mailbox.status = 'active'
        AND mailbox.user_id = owner.id
      LIMIT 1
    `,
    [hashApiKey(normalizedSecret)],
  );
  const authenticated = rows[0];

  if (!authenticated) {
    return null;
  }

  const growthPlanAvailable = await hasGrowthPlan(authenticated);

  // Avoid a database write for every API request while keeping useful activity data.
  if (growthPlanAvailable) {
    await getDbPool().query(
      `
        UPDATE mailbox_developer_api_keys
        SET last_used_at = NOW(), updated_at = NOW()
        WHERE id = ?
          AND (last_used_at IS NULL OR last_used_at < DATE_SUB(NOW(), INTERVAL 5 MINUTE))
      `,
      [authenticated.api_key_id],
    );
  }

  return {
    apiKeyId: Number(authenticated.api_key_id),
    growthPlanAvailable,
    mailboxEmail: authenticated.mailbox_email,
  };
}

export async function getMailboxDeveloperMessageStatus(email: string, messageId: number) {
  if (!Number.isSafeInteger(messageId) || messageId <= 0) {
    return null;
  }

  const account = await getMailboxAccountByEmail(email);

  if (!account) {
    return null;
  }

  const [messageRows] = await getDbPool().query<DeveloperMessageStatusRow[]>(
    `
      SELECT
        id,
        from_address,
        to_addresses,
        subject,
        message_id_header,
        transport_status,
        transport_response,
        created_at,
        updated_at
      FROM mailbox_messages
      WHERE id = ?
        AND mailbox_id = ?
        AND direction = 'outbound'
      LIMIT 1
    `,
    [messageId, account.mailbox_id],
  );
  const message = messageRows[0];

  if (!message) {
    return null;
  }

  const [logRows] = await getDbPool().query<DeveloperMessageDeliveryLogRow[]>(
    `
      SELECT
        id,
        status,
        transport,
        target_address,
        message_id_header,
        response_text,
        created_at
      FROM mailbox_delivery_logs
      WHERE mailbox_message_id = ?
        AND mailbox_id = ?
        AND action = 'send'
      ORDER BY created_at ASC, id ASC
    `,
    [message.id, account.mailbox_id],
  );

  return {
    createdAt: message.created_at.toISOString(),
    from: message.from_address,
    logs: logRows.map((log) => ({
      createdAt: log.created_at.toISOString(),
      id: Number(log.id),
      messageIdHeader: log.message_id_header,
      response: log.response_text,
      status: log.status,
      target: log.target_address,
      transport: log.transport,
    })),
    messageId: Number(message.id),
    messageIdHeader: message.message_id_header,
    response: message.transport_response,
    status: message.transport_status,
    subject: message.subject,
    to: message.to_addresses,
    updatedAt: message.updated_at.toISOString(),
  } satisfies MailDeveloperMessageStatus;
}
