// How urgent a domain is. Derived on every read from the stored dates, so the
// status moves day by day without a new lookup.

export const WARNING_DAYS = 30;
export const CRITICAL_DAYS = 7;

export type DomainLookupResult = "FOUND" | "NOT_FOUND" | "UNSUPPORTED" | "FAILED";

export type DomainStatus = "NOT_REGISTERED" | "EXPIRED" | "CRITICAL" | "UNKNOWN" | "PENDING" | "WARNING" | "OK";

export const DOMAIN_STATUS_LABELS: Record<DomainStatus, string> = {
  NOT_REGISTERED: "Not Registered",
  EXPIRED: "Expired",
  CRITICAL: "Critical",
  UNKNOWN: "Unknown",
  PENDING: "Not checked yet",
  WARNING: "Expiring Soon",
  OK: "OK",
};

export const DOMAIN_STATUS_HINTS: Partial<Record<DomainStatus, string>> = {
  NOT_REGISTERED: "The registry has no record of this domain",
  CRITICAL: `Expires within ${CRITICAL_DAYS} days, or the registry flags it for deletion / hold`,
  UNKNOWN: "No expiry date is available — see the note on the row",
  WARNING: `Expires within ${WARNING_DAYS} days`,
};

// Listing order: problems first, then by expiry date. Unknown sits right after
// Critical because a domain nobody can see the expiry of is a risk too.
const RANK: Record<DomainStatus, number> = {
  NOT_REGISTERED: 0,
  EXPIRED: 1,
  CRITICAL: 2,
  UNKNOWN: 3,
  PENDING: 4,
  WARNING: 5,
  OK: 6,
};

/** At or worse than "Expiring Soon": what the dashboard and app pages call out. */
export function needsAttention(status: DomainStatus): boolean {
  return RANK[status] <= RANK.WARNING && status !== "PENDING";
}

/**
 * Registry status codes (EPP, as RDAP spells them) that mean trouble whatever
 * the expiry date says. "client … prohibited" codes are ordinary registrar locks
 * and deliberately not listed.
 */
const STATUS_ESCALATIONS: Record<string, "CRITICAL" | "WARNING"> = {
  "redemption period": "CRITICAL",
  "pending delete": "CRITICAL",
  "pending restore": "CRITICAL",
  "client hold": "CRITICAL",
  "server hold": "CRITICAL",
  "pending transfer": "WARNING",
};

export function riskyRegistryStatuses(statuses: string[]): string[] {
  return statuses.filter((status) => status in STATUS_ESCALATIONS);
}

export type DomainState = {
  expiresAt: Date | null;
  manualExpiresAt: Date | null;
  registryStatuses: string[];
  lastLookupResult: DomainLookupResult | null;
};

export type DomainAssessment = {
  status: DomainStatus;
  /** The registry's date, else the one entered by hand. */
  expiry: Date | null;
  expirySource: "registry" | "manual" | null;
  /** Whole days until expiry, negative once expired. */
  daysLeft: number | null;
};

const DAY_MS = 86_400_000;

export function assessDomain(domain: DomainState | null, now: Date): DomainAssessment {
  const expiry = domain?.expiresAt ?? domain?.manualExpiresAt ?? null;
  const expirySource = domain?.expiresAt ? "registry" : domain?.manualExpiresAt ? "manual" : null;
  const daysLeft = expiry ? Math.floor((expiry.getTime() - now.getTime()) / DAY_MS) : null;

  let status: DomainStatus;
  if (!domain || (domain.lastLookupResult === null && !expiry)) status = "PENDING";
  else if (domain.lastLookupResult === "NOT_FOUND") status = "NOT_REGISTERED";
  else if (daysLeft === null) status = "UNKNOWN";
  else if (expiry!.getTime() <= now.getTime()) status = "EXPIRED";
  else if (daysLeft <= CRITICAL_DAYS) status = "CRITICAL";
  else if (daysLeft <= WARNING_DAYS) status = "WARNING";
  else status = "OK";

  // A risky registry status raises the level, never lowers it.
  for (const code of riskyRegistryStatuses(domain?.registryStatuses ?? [])) {
    const floor = STATUS_ESCALATIONS[code];
    if (RANK[floor] < RANK[status]) status = floor;
  }

  return { status, expiry, expirySource, daysLeft };
}

/** Most urgent first; within a status, the nearest expiry first; then by name. */
export function compareDomains(
  a: { name: string } & Pick<DomainAssessment, "status" | "expiry">,
  b: { name: string } & Pick<DomainAssessment, "status" | "expiry">,
): number {
  return (
    RANK[a.status] - RANK[b.status] ||
    (a.expiry?.getTime() ?? Number.MAX_SAFE_INTEGER) - (b.expiry?.getTime() ?? Number.MAX_SAFE_INTEGER) ||
    a.name.localeCompare(b.name)
  );
}

/** "in 7 days", "today", "1 day ago". */
export function formatDaysLeft(daysLeft: number): string {
  if (daysLeft === 0) return "today";
  const n = Math.abs(daysLeft);
  const unit = `${n} ${n === 1 ? "day" : "days"}`;
  return daysLeft > 0 ? `in ${unit}` : `${unit} ago`;
}
