import { createCoreAPIClient } from "@/lib/features/coreClient";
import type { CoreResponse } from "@/lib/features/types";

/**
 * Retiring a member account.
 *
 * "Deletion" is a status move in Viewpoint, mirrored into the member database — no
 * row is ever removed. Viewpoint is the system of record and is written first; if it
 * refuses, nothing local changes.
 *
 * Every call here is super-admin only and core enforces that itself. A non-super
 * admin gets a 404 rather than a 403, because the console's `coreClient` treats a
 * 403 as a dead session and would sign them out.
 *
 * Reads are POST: the search carries an account number or an email, and neither
 * belongs in a URL that gets logged and pasted into tickets.
 */

const coreClient = createCoreAPIClient();

const BASE = "/v1/admin-console/account-deletion";

/** What decided an account's region. */
export type RegionSource = "charge-centre" | "country" | "fallback";

/** Which region's letters a deletion sends. */
export type DeletionRegion = "uk" | "europe" | "india" | "indonesia" | "others";

/** A target status, as core knows it. */
export interface DeletionStatus {
  id: string;
  name: string;
  /** The Viewpoint status id this maps to. Null means it cannot be applied. */
  extAccountStatusID: string | null;
  isActive: boolean;
  /**
   * Whether it can be used at all.
   *
   * False when no Viewpoint id is mapped. Shown disabled rather than hidden, so an
   * operator looking for a status they expected sees why it is unavailable instead
   * of assuming the list is wrong.
   */
  applicable: boolean;
}

export interface AccountContact {
  contactID: number;
  name: string;
  email: string | null;
  isOwner: boolean;
  isPrimary: boolean;
  active: boolean;
}

export interface LocalMemberRow {
  memberID: string;
  memberNumber: string;
  userID: string;
  email: string | null;
  firstName: string | null;
  lastName: string | null;
  isOwner: boolean | null;
  isActive: boolean | null;
  isDeleted: boolean | null;
  userIsActive: boolean | null;
  country: string | null;
  accountTypeName: string;
  accountStatusID: string;
  accountStatusName: string;
}

/**
 * One account, as both systems currently see it.
 *
 * The two halves are kept apart rather than merged. They disagreeing is the useful
 * signal — a local status that trails Viewpoint means the last sync did not land,
 * and merging them would hide exactly that.
 */
export interface AccountSearchResult {
  accountNumber: string;
  viewpoint: {
    found: boolean;
    accountName: string | null;
    accountType: string | null;
    accountStatus: string | null;
    accountStatusID: number | null;
    joinedDate: string | null;
    country: string | null;
    chargeCentreID: string | null;
    chargeCentreName: string | null;
    contacts: AccountContact[];
  };
  local: {
    found: boolean;
    members: LocalMemberRow[];
    activeSessions: number;
  };
  /**
   * Which region's deletion letters this account would get.
   *
   * Detected from the account's country by core, not decided: it is pre-selected in
   * the confirmation dialog and the operator can change it. No country on file
   * resolves to "others".
   */
  region: DeletionRegion;
  /**
   * What decided the region.
   *
   * "fallback" means neither the charge centre nor the country said anything and the
   * account landed on "Rest of world" by default — a different thing from a member
   * who genuinely is outside the named regions, and only one of the two is worth
   * correcting.
   */
  regionSource: RegionSource;
}

export interface AccountDeletionOutcome {
  accountNumber: string;
  /**
   * True when this corrected the account's existing closure rather than opening a
   * new one, in which case no acknowledgement email was queued.
   */
  updatedExisting: boolean;
  noticeSent: boolean;
  appliedStatus: { id: string; name: string; extID: number };
  viewpoint: {
    changed: boolean;
    previousStatus: string | null;
    previousStatusID: number | null;
    currentStatus: string | null;
  };
  local: {
    membersUpdated: number;
    usersUpdated: number;
    sessionsRevoked: number;
  };
  /** Non-null means Viewpoint moved and the member database did not. */
  localError: string | null;
  /** Non-null means the AUDIT note in Viewpoint's member log did not get written. */
  viewpointLogError: string | null;
  region: DeletionRegion;
  followupDays: number;
  reason: string;
  before: AccountSearchResult;
  after: AccountSearchResult;
}

/**
 * Finds accounts by number or by email — exactly one of the two.
 *
 * An account number identifies one account. An email does not: a household shares
 * one and a person can own several memberships, so that search returns a list for
 * the operator to choose from rather than picking for them.
 */
export async function searchAccounts(
  input: { accountNumber: string } | { email: string },
): Promise<CoreResponse<{ items: AccountSearchResult[]; total: number }>> {
  return await coreClient(`${BASE}/search`, {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function listDeletionStatuses(): Promise<
  CoreResponse<{
    items: DeletionStatus[];
    /**
     * Expected Viewpoint status ids the member database did not have.
     *
     * Surfaced so a short list cannot pass for a complete one — a dropdown with only
     * "Invalid" in it looks like it is working.
     */
    missingExtStatusIDs: string[];
  }>
> {
  return await coreClient(`${BASE}/statuses`, {
    method: "POST",
    body: JSON.stringify({}),
  });
}

/**
 * Applies the retirement.
 *
 * A 207 response means Viewpoint was updated and the member database was not. That
 * is a partial success, not a failure, and must not be retried blindly.
 */
export async function applyAccountDeletion(input: {
  accountNumber: string;
  statusID: string;
  reason: string;
  killLogin: boolean;
  revokeSessions: boolean;
  softDelete: boolean;
  region: DeletionRegion;
  /** Omit or leave empty to send the region's own letter. */
  noticeTemplateID?: string;
  followupTemplateID?: string;
}): Promise<CoreResponse<AccountDeletionOutcome>> {
  return await coreClient(`${BASE}/apply`, {
    method: "POST",
    body: JSON.stringify(input),
  });
}

/**
 * How a mail for one record ended up.
 *
 * `skipped` and `cancelled` are not the same thing: skipped means it could not be
 * sent (no template configured, no address on file), cancelled means it should not
 * be — the account was reactivated before the 30 days were up.
 */
export type EdmStatus =
  | "pending"
  | "sent"
  | "failed"
  | "skipped"
  | "cancelled";

/**
 * One past deletion.
 *
 * The two mails are tracked separately because they happen a month apart and can
 * end differently — the notice going out says nothing about whether the follow-up
 * will, and a single combined state would hide that.
 */
export interface DeletionRecord {
  id: string;
  accountNumber: string;
  accountName: string | null;
  statusName: string;
  previousStatus: string | null;
  reason: string;
  killLogin: boolean;
  revokeSessions: boolean;
  softDelete: boolean;
  membersUpdated: number;
  usersUpdated: number;
  sessionsRevoked: number;
  /** Non-null means Viewpoint moved and the member database did not. */
  localError: string | null;
  performedBy: string | null;
  recipients: string[];
  noticeEdmStatus: EdmStatus;
  noticeEdmAt: string | null;
  noticeEdmError: string | null;
  followupDueAt: string;
  followupEdmStatus: EdmStatus;
  followupEdmAt: string | null;
  followupEdmError: string | null;
  reactivatedAt: string | null;
  reactivatedBy: string | null;
  reactivatedToStatus: string | null;
  createdAt: string;
  updatedAt: string | null;
  region: DeletionRegion | null;
  followupDays: number | null;
  /** 'sent' | 'failed'; null on rows written before the Viewpoint log existed. */
  viewpointLogStatus: string | null;
  viewpointLogError: string | null;
  noticeTemplateID?: string | null;
  followupTemplateID?: string | null;
  /**
   * Whether core will accept a reactivation for this record.
   *
   * False once it has been reactivated, and false when the record never captured the
   * status the account held before — there is nothing to restore it to. Decided by
   * core so the button and the endpoint cannot disagree.
   */
  canReactivate: boolean;
}

export async function listDeletionRecords(query: {
  page: number;
  pageSize: number;
  search?: string;
}): Promise<
  CoreResponse<{
    items: DeletionRecord[];
    pagination: { page: number; pageSize: number; total: number };
  }>
> {
  return await coreClient(`${BASE}/records`, {
    method: "POST",
    body: JSON.stringify(query),
  });
}

/**
 * Puts a retired account back to the status it held before.
 *
 * Reverses Viewpoint first, then the local record, and cancels the pending 30-day
 * mail. Refused by core when the record does not say what the previous status was —
 * `canReactivate` on the record is the same answer, ahead of time.
 */
export async function reactivateAccount(
  recordID: string,
): Promise<
  CoreResponse<{
    recordID: string;
    accountNumber: string;
    restoredStatus: { id: string; name: string; extID: string };
    followupCancelled: boolean;
    /** Non-null means the AUDIT note in Viewpoint's member log did not get written. */
    viewpointLogError: string | null;
    local: { membersUpdated: number; usersUpdated: number };
  }>
> {
  return await coreClient(`${BASE}/reactivate`, {
    method: "POST",
    body: JSON.stringify({ recordID }),
  });
}

export interface RegionTemplates {
  region: DeletionRegion;
  label: string;
  /** Sent when the account is retired. */
  acknowledgement: { id: string; name: string };
  /** Sent 30 days later, unless the account is reactivated first. */
  confirmation: { id: string; name: string };
}

/**
 * The region catalogue, straight from core.
 *
 * Fetched rather than duplicated here: the ids core sends are the same constants it
 * sends the mail with, so the dialog cannot describe one template and send another.
 */
export interface DeletionConfig {
  regions: RegionTemplates[];
  followupDays: number;
}

export async function getDeletionConfig(): Promise<
  CoreResponse<DeletionConfig>
> {
  return await coreClient(`${BASE}/config`, {
    method: "POST",
    body: JSON.stringify({}),
  });
}

/**
 * Sends one of a record's two emails now.
 *
 * `notice` is a resend of the acknowledgement; `followup` brings the data-deletion
 * confirmation forward and marks it sent, so the nightly sweep will not send it
 * again. Uses the template and addresses already on the record.
 */
export async function sendRecordMailNow(input: {
  recordID: string;
  kind: "notice" | "followup";
}): Promise<
  CoreResponse<{
    recordID: string;
    kind: "notice" | "followup";
    templateID: string;
    recipients: string[];
  }>
> {
  return await coreClient(`${BASE}/send-now`, {
    method: "POST",
    body: JSON.stringify(input),
  });
}

/**
 * Sends a deletion letter to any address, to prove the path works.
 *
 * Goes onto the same SMTP queue as a real send, with the same template. Nothing is
 * recorded against any account — no member was involved.
 */
export async function sendTestDeletionMail(input: {
  templateID: string;
  /** Up to ten; each gets its own message so testers do not see each other. */
  to: string[];
  accountNumber?: string;
  accountName?: string;
  statusName?: string;
}): Promise<
  CoreResponse<{
    templateID: string;
    sent: string[];
    failed: Array<{ to: string; message: string }>;
  }>
> {
  return await coreClient(`${BASE}/test-send`, {
    method: "POST",
    body: JSON.stringify(input),
  });
}

/**
 * Saves the follow-up window.
 *
 * Applies to deletions from now on. Mail already promised keeps the date the member
 * was given, so shortening the window never pulls a queued letter forward.
 */
export async function saveDeletionConfig(
  followupDays: number,
): Promise<CoreResponse<{ followupDays: number }>> {
  return await coreClient(`${BASE}/config`, {
    method: "PUT",
    body: JSON.stringify({ followupDays }),
  });
}

/**
 * An account that is currently in a deletion status.
 *
 * `source` is the important field. "console" means this system retired it and can
 * account for it — reason, operator, both emails. "external" means it arrived in
 * that status another way (a direct change in Viewpoint, or a sync), so nothing was
 * emailed and no data-deletion email is scheduled.
 */
export interface RetiredAccount {
  accountNumber: string;
  name: string | null;
  statusName: string;
  memberCount: number;
  statusChangedAt: string | null;
  source: "console" | "external";
  record: {
    id: string;
    reason: string;
    performedBy: string | null;
    region: DeletionRegion | null;
    createdAt: string;
    noticeEdmStatus: EdmStatus;
    followupEdmStatus: EdmStatus;
    followupDueAt: string;
    followupDays: number | null;
    reactivatedAt: string | null;
    canReactivate: boolean;
  } | null;
}

/**
 * Every retired account, one row each.
 *
 * Drawn from the member database rather than the console's own records, so an
 * account retired directly in Viewpoint appears too — and a household of four
 * members is one row, not four.
 */
export async function listRetiredAccounts(query: {
  page: number;
  pageSize: number;
  search?: string;
}): Promise<
  CoreResponse<{
    items: RetiredAccount[];
    pagination: { page: number; pageSize: number; total: number };
  }>
> {
  return await coreClient(`${BASE}/retired`, {
    method: "POST",
    body: JSON.stringify(query),
  });
}
