import "server-only";

import { randomUUID } from "crypto";
import { getManagedMembersByOwnerEmail } from "@/lib/mail-automation";
import {
  countMailBillingSeatsFromManagedMembers,
  createMailBillingPreview,
} from "@/lib/mail-billing";

export const TOSS_CONFIRM_API_URL = "https://api.tosspayments.com/v1/payments/confirm";
export const TOSS_BILLING_AUTHORIZATION_API_URL =
  "https://api.tosspayments.com/v1/billing/authorizations/issue";
export const TOSS_BILLING_API_URL = "https://api.tosspayments.com/v1/billing";
const TOSS_DOCS_TEST_CLIENT_KEY = "test_ck_D5GePWvyJnrK0W0k6q8gLzN97Eoq";
const TOSS_DOCS_TEST_SECRET_KEY = "test_sk_zXLkKEypNArWmo50nX3lmeaxYG5R";

export type TossPaymentsKeySource = "docs-test" | "env";
export type TossPaymentsEnvironment = "live" | "test";

export type TossPaymentsBilling = {
  authenticatedAt?: string;
  billingKey: string;
  card?: {
    acquirerCode?: string;
    cardType?: string;
    issuerCode?: string;
    number?: string;
    ownerType?: string;
  };
  customerKey: string;
  mId?: string;
  method?: string;
};

export type TossPaymentsPayment = {
  approvedAt?: string;
  cancels?: Array<{
    canceledAt?: string;
    cancelAmount?: number;
    cancelStatus?: string;
    transactionKey?: string;
  }>;
  card?: {
    acquirerCode?: string;
    cardType?: string;
    issuerCode?: string;
    number?: string;
    ownerType?: string;
  };
  currency?: string;
  method?: string;
  orderId?: string;
  orderName?: string;
  paymentKey: string;
  status?: string;
  totalAmount?: number;
};

type TossPaymentsApiError = {
  code?: string;
  message?: string;
};

export class TossPaymentsRequestError extends Error {
  code: string;
  status: number;

  constructor(input: { code?: string; message?: string; status: number }) {
    super(input.message || "토스페이먼츠 요청에 실패했습니다.");
    this.name = "TossPaymentsRequestError";
    this.code = input.code || `HTTP_${input.status}`;
    this.status = input.status;
  }
}

export type TossBillingCheckoutSession = {
  amount: number;
  billableSeats: number;
  clientKey: string;
  clientKeySource: TossPaymentsKeySource;
  customerKey: string;
  estimatedMonthlyCost: number;
  failUrl: string;
  hasSecretKey: boolean;
  orderId: string;
  orderName: string;
  secretKeySource: TossPaymentsKeySource;
  successUrl: string;
  usesFallbackAmount: boolean;
};

export function getTossPaymentsEnvironment(): TossPaymentsEnvironment {
  return process.env.TOSS_PAYMENTS_MODE?.trim().toLowerCase() === "live"
    ? "live"
    : "test";
}

function getConfiguredKey(name: string, docsTestValue: string) {
  const value = process.env[name]?.trim();

  if (value) {
    return value;
  }

  if (getTossPaymentsEnvironment() === "test") {
    return docsTestValue;
  }

  throw new Error(`${name} is required when TOSS_PAYMENTS_MODE=live.`);
}

export function getTossPaymentsClientKey() {
  return getConfiguredKey(
    "NEXT_PUBLIC_TOSS_PAYMENTS_CLIENT_KEY",
    TOSS_DOCS_TEST_CLIENT_KEY,
  );
}

export function getTossPaymentsClientKeySource(): TossPaymentsKeySource {
  const value = process.env.NEXT_PUBLIC_TOSS_PAYMENTS_CLIENT_KEY?.trim();
  return value ? "env" : "docs-test";
}

export function getTossPaymentsSecretKey() {
  return getConfiguredKey("TOSS_PAYMENTS_SECRET_KEY", TOSS_DOCS_TEST_SECRET_KEY);
}

export function getTossPaymentsSecretKeySource(): TossPaymentsKeySource {
  const value = process.env.TOSS_PAYMENTS_SECRET_KEY?.trim();
  return value ? "env" : "docs-test";
}

export async function buildTossBillingCheckoutSession(input: {
  email: string;
  origin: string;
}): Promise<TossBillingCheckoutSession> {
  const members = await getManagedMembersByOwnerEmail(input.email).catch(() => []);
  const preview = createMailBillingPreview(
    countMailBillingSeatsFromManagedMembers(members.length),
  );
  const origin = input.origin.replace(/\/$/, "");

  return {
    amount: preview.chargeAmount,
    billableSeats: preview.billableSeats,
    clientKey: getTossPaymentsClientKey(),
    clientKeySource: getTossPaymentsClientKeySource(),
    customerKey: createTossCustomerKey(),
    estimatedMonthlyCost: preview.estimatedMonthlyCost,
    failUrl: `${origin}/payments/toss/fail`,
    hasSecretKey: Boolean(getTossPaymentsSecretKey()),
    orderId: createTossOrderId(),
    orderName:
      `성장플랜 ${preview.billableSeats}명`,
    secretKeySource: getTossPaymentsSecretKeySource(),
    successUrl: `${origin}/payments/toss/success`,
    usesFallbackAmount: preview.usesFallbackAmount,
  };
}

export async function getExpectedTossBillingAmountByEmail(email: string) {
  const members = await getManagedMembersByOwnerEmail(email).catch(() => []);
  return createMailBillingPreview(
    countMailBillingSeatsFromManagedMembers(members.length),
  );
}

export function encodeTossSecretKey(secretKey: string) {
  return Buffer.from(`${secretKey}:`).toString("base64");
}

export function createTossCustomerKey() {
  return `om_${randomUUID().replace(/-/g, "")}`;
}

function createTossOrderId() {
  return `om_${Date.now()}_${randomUUID().replace(/-/g, "").slice(0, 18)}`;
}

async function requestTossPayments<TResponse>(input: {
  body?: Record<string, unknown>;
  idempotencyKey?: string;
  method: "DELETE" | "POST";
  url: string;
}) {
  const response = await fetch(input.url, {
    method: input.method,
    headers: {
      Authorization: `Basic ${encodeTossSecretKey(getTossPaymentsSecretKey())}`,
      "Content-Type": "application/json",
      ...(input.idempotencyKey
        ? { "Idempotency-Key": input.idempotencyKey }
        : {}),
    },
    body: input.body ? JSON.stringify(input.body) : undefined,
    cache: "no-store",
    signal: AbortSignal.timeout(65_000),
  });
  const rawText = await response.text();
  let data: TResponse | TossPaymentsApiError | null = null;

  if (rawText) {
    try {
      data = JSON.parse(rawText) as TResponse | TossPaymentsApiError;
    } catch {
      if (response.ok) {
        throw new TossPaymentsRequestError({
          code: "INVALID_PROVIDER_RESPONSE",
          message: "토스페이먼츠가 올바르지 않은 응답을 반환했습니다.",
          status: response.status,
        });
      }
    }
  }

  if (!response.ok) {
    const error = data as TossPaymentsApiError | null;
    throw new TossPaymentsRequestError({
      code: error?.code,
      message: error?.message || rawText,
      status: response.status,
    });
  }

  return {
    data: data as TResponse,
    rawText,
    status: response.status,
  };
}

export async function issueTossPaymentsBillingKey(input: {
  authKey: string;
  customerKey: string;
}) {
  return requestTossPayments<TossPaymentsBilling>({
    body: {
      authKey: input.authKey,
      customerKey: input.customerKey,
    },
    method: "POST",
    url: TOSS_BILLING_AUTHORIZATION_API_URL,
  });
}

export async function chargeTossPaymentsBillingKey(input: {
  amount: number;
  billingKey: string;
  customerEmail: string;
  customerKey: string;
  orderId: string;
  orderName: string;
  taxFreeAmount?: number;
}) {
  return requestTossPayments<TossPaymentsPayment>({
    body: {
      amount: input.amount,
      customerEmail: input.customerEmail,
      customerKey: input.customerKey,
      orderId: input.orderId,
      orderName: input.orderName,
      taxFreeAmount: input.taxFreeAmount ?? 0,
    },
    idempotencyKey: `billing:${input.orderId}`,
    method: "POST",
    url: `${TOSS_BILLING_API_URL}/${encodeURIComponent(input.billingKey)}`,
  });
}

export async function cancelTossPaymentsPayment(input: {
  amount?: number;
  cancelReason: string;
  idempotencyKey: string;
  paymentKey: string;
}) {
  return requestTossPayments<TossPaymentsPayment>({
    body: {
      cancelReason: input.cancelReason,
      ...(input.amount === undefined ? {} : { cancelAmount: input.amount }),
    },
    idempotencyKey: input.idempotencyKey,
    method: "POST",
    url: `https://api.tosspayments.com/v1/payments/${encodeURIComponent(input.paymentKey)}/cancel`,
  });
}

export async function deleteTossPaymentsBillingKey(billingKey: string) {
  return requestTossPayments<null>({
    method: "DELETE",
    url: `${TOSS_BILLING_API_URL}/${encodeURIComponent(billingKey)}`,
  });
}
