import type {
  PaymentTransferSummary,
  TokenTransaction,
  TokenTransactionListItem,
} from "@sdp/types";
import type { MessageKey, TranslationValues } from "@/i18n/messages";
import type { SdpApiClient } from "@/lib/sdp-api";
import { parseErrorMessage, readTransactionParam, toTitleCase } from "./activity-format-utils";
import { resolveTransferTokenLabel } from "./payments/payments-overview.utils";
import type { FetchResult } from "./payments/payments-page.data";

type Translate = (key: MessageKey, values?: TranslationValues) => string;

export interface HomeActivityExplorerRef {
  kind: "tx" | "address";
  value: string;
}

export interface HomeActivityRow {
  id: string;
  createdAt: string;
  type: string;
  token: string;
  /**
   * Raw mint behind `token`, when there is one. `token` is already resolved here,
   * but this builder only sees issued-token symbols — a holding the organization
   * did not issue degrades to a shortened mint. Carrying the mint lets the client,
   * which has the symbols that came back with the balances, name it properly.
   */
  tokenMint: string | null;
  amount: string;
  address: string;
  explorer: HomeActivityExplorerRef | null;
  sourceKind: "payments" | "issuance";
}

function resolveIssuanceAmount(transaction: TokenTransaction): string {
  const amount = readTransactionParam(transaction.params, "amount");
  if (amount === null) {
    return "—";
  }
  return String(amount);
}

function resolveIssuanceAddress(transaction: TokenTransaction): string {
  const destination = readTransactionParam(transaction.params, "destination");
  if (destination !== null) {
    return String(destination);
  }

  const source = readTransactionParam(transaction.params, "source");
  if (source !== null) {
    return String(source);
  }

  if (transaction.signature) {
    return transaction.signature;
  }

  return "—";
}

function resolvePaymentsType(transfer: PaymentTransferSummary, t: Translate): string {
  if (transfer.direction === "outbound") {
    return t("Shared.homeWorkspace.send");
  }

  if (transfer.direction === "inbound") {
    return t("Shared.homeWorkspace.receive");
  }

  return transfer.type ? toTitleCase(transfer.type) : t("Shared.homeWorkspace.transfer");
}

function resolvePaymentsAddress(transfer: PaymentTransferSummary): string {
  if (transfer.direction === "outbound") {
    return transfer.destination ?? "—";
  }

  if (transfer.direction === "inbound") {
    return transfer.source ?? "—";
  }

  return transfer.destination ?? transfer.source ?? "—";
}

// Prefer the counterparty address (what the row shows) and fall back to the tx signature so the
// row is still linkable before an address is known. Cluster is applied later, in the client.
function resolvePaymentsExplorer(transfer: PaymentTransferSummary): HomeActivityExplorerRef | null {
  const address = resolvePaymentsAddress(transfer);
  if (address !== "—") {
    return { kind: "address", value: address };
  }
  if (transfer.signature) {
    return { kind: "tx", value: transfer.signature };
  }
  return null;
}

function resolveIssuanceExplorer(transaction: TokenTransaction): HomeActivityExplorerRef | null {
  const destination = readTransactionParam(transaction.params, "destination");
  if (destination !== null) {
    return { kind: "address", value: String(destination) };
  }
  const source = readTransactionParam(transaction.params, "source");
  if (source !== null) {
    return { kind: "address", value: String(source) };
  }
  if (transaction.signature) {
    return { kind: "tx", value: transaction.signature };
  }
  return null;
}

export function computeTodaysVolume(transfers: PaymentTransferSummary[]): number | null {
  const now = new Date();
  const startOfDay = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
  let hasNumericAmount = false;

  const total = transfers.reduce((sum, transfer) => {
    if (!transfer.createdAt) {
      return sum;
    }

    const createdAt = new Date(transfer.createdAt);
    if (Number.isNaN(createdAt.getTime()) || createdAt < startOfDay) {
      return sum;
    }

    const amount = Number(transfer.amount);
    if (!Number.isFinite(amount)) {
      return sum;
    }

    hasNumericAmount = true;
    return sum + amount;
  }, 0);

  return hasNumericAmount ? total : null;
}

export function buildHomeActivityRows(
  transfers: PaymentTransferSummary[],
  issuanceTransactions: TokenTransactionListItem[],
  t: Translate,
  /** Symbols for tokens this org issued, keyed by mint. The catalogue never knows them. */
  issuedTokenSymbolsByMint?: Readonly<Record<string, string>>
): HomeActivityRow[] {
  const paymentRows = transfers
    .filter(
      (transfer): transfer is PaymentTransferSummary & { createdAt: string } =>
        typeof transfer.createdAt === "string" && transfer.createdAt.length > 0
    )
    .map((transfer) => ({
      id: `payment-${transfer.id}`,
      createdAt: transfer.createdAt,
      type: resolvePaymentsType(transfer, t),
      // transfer.token is a mint address; without this the row renders the raw
      // base58 while the Transactions table shows the symbol for the same row.
      token: resolveTransferTokenLabel(transfer.token, issuedTokenSymbolsByMint) ?? "—",
      tokenMint: transfer.token?.trim() || null,
      amount: transfer.amount ?? "—",
      address: resolvePaymentsAddress(transfer),
      explorer: resolvePaymentsExplorer(transfer),
      sourceKind: "payments" as const,
    }));

  const issuanceRows = issuanceTransactions
    .filter(
      (
        entry
      ): entry is typeof entry & {
        transaction: TokenTransaction & { createdAt: string };
      } => typeof entry.transaction.createdAt === "string" && entry.transaction.createdAt.length > 0
    )
    .map(({ token, transaction }) => ({
      id: `issuance-${transaction.id}`,
      createdAt: transaction.createdAt,
      type: toTitleCase(transaction.type),
      token: token.symbol || token.name || "—",
      tokenMint: token.mintAddress?.trim() || null,
      amount: resolveIssuanceAmount(transaction),
      address: resolveIssuanceAddress(transaction),
      explorer: resolveIssuanceExplorer(transaction),
      sourceKind: "issuance" as const,
    }));

  return [...paymentRows, ...issuanceRows]
    .sort((left, right) => {
      return new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime();
    })
    .slice(0, 10);
}

export async function fetchOrgIssuanceActivity(
  request: SdpApiClient["request"],
  t: Translate,
  pageSize = 20
): Promise<FetchResult<TokenTransactionListItem[]>> {
  try {
    const response = await request(
      `/v1/issuance/transactions?${new URLSearchParams({
        page: "1",
        pageSize: String(pageSize),
      }).toString()}`
    );
    if (!response.ok) {
      const body = await response.text();
      return {
        ok: false,
        status: response.status,
        error: parseErrorMessage(body),
      };
    }

    const json = (await response.json()) as { data?: TokenTransactionListItem[] };
    return { ok: true, data: json.data ?? [] };
  } catch (error) {
    return {
      ok: false,
      error:
        error instanceof Error
          ? error.message
          : t("Shared.homeWorkspace.issuanceActivityUnavailable"),
    };
  }
}
