import { api as client } from "@/auth/http";
import { URL_CONSTANTS } from "@/constants/URLS";
import type {
  RuleEngineListMeta,
  RuleEngineListResult,
  RuleEngineRecord,
  RuleEngineResourceSlug,
} from "@/types/rule-engine";
import { unwrap } from "@/utils/endpoint";

export interface RuleEngineListParams {
  search?: string;
  page?: number;
  pageSize?: number;
  isActive?: boolean;
  status?: string;
  sortBy?: string;
  sortOrder?: "ASC" | "DESC";
  requiresDirectorApproval?: boolean;
  /** Rates category tabs — comma-separated appliesTo / trigger filters. */
  appliesTo?: string;
  trigger?: string;
}

export interface RuleEngineReorderPayload {
  ids: string[];
  requiresDirectorApproval?: boolean;
}

/** Priority-rule approval workflow (all priority-config changes go through it). */
const PRIORITY_RULE_CHANGES_BASE = "/priority-rule-change-requests";

export interface PriorityRuleChangeRequest {
  id: string;
  action: "CREATE" | "UPDATE" | "DELETE";
  priorityConfigId: string | null;
  priorityConfig?: RuleEngineRecord | null;
  payload: Record<string, unknown> | null;
  status: "PENDING" | "APPROVED" | "REJECTED";
  requestedByUserId: string | null;
  decidedByUserId: string | null;
  decidedAt: string | null;
  decisionNote: string | null;
  createdAt: string;
}

export interface SubmitPriorityRuleChangePayload {
  action: "CREATE" | "UPDATE" | "DELETE";
  priorityConfigId?: string;
  create?: Record<string, unknown>;
  update?: Record<string, unknown>;
}

/** Approval workflow for edits to LIVE rates — a DRAFT rate still edits directly. */
const RATE_CHANGES_BASE = "/rate-change-requests";

export interface RateChangeRequest {
  id: string;
  rateId: string;
  rate?: RuleEngineRecord | null;
  /** Changed fields only. */
  payload: Record<string, unknown>;
  /** What those same fields were when the change was filed. */
  previousValues: Record<string, unknown>;
  status: "PENDING" | "APPROVED" | "REJECTED";
  requestedByUserId: string | null;
  decidedByUserId: string | null;
  decidedAt: string | null;
  decisionNote: string | null;
  createdAt: string;
}

export interface SubmitRateChangePayload {
  rateId: string;
  update: Record<string, unknown>;
}

/** One selectable IAM position type, as returned by /approval-rules/position-types. */
export interface ApprovalPositionType {
  label: string;
  value: string;
}

const RESOURCE_BASE: Record<RuleEngineResourceSlug, string> = {
  "cargo-types": URL_CONSTANTS.RULE_ENGINE.CARGO_TYPES,
  "container-types": URL_CONSTANTS.RULE_ENGINE.CONTAINER_TYPES,
  "wagon-types": URL_CONSTANTS.RULE_ENGINE.WAGON_TYPES,
  "truck-types": URL_CONSTANTS.RULE_ENGINE.TRUCK_TYPES,
  "priority-configs": URL_CONSTANTS.RULE_ENGINE.PRIORITY_CONFIGS,
  "service-types": URL_CONSTANTS.RULE_ENGINE.SERVICE_TYPES,
  "weight-limit-rules": URL_CONSTANTS.RULE_ENGINE.WEIGHT_LIMIT_RULES,
  yards: URL_CONSTANTS.RULE_ENGINE.YARDS,
  "yard-distances": URL_CONSTANTS.RULE_ENGINE.YARD_DISTANCES,
  "shipping-lines": URL_CONSTANTS.RULE_ENGINE.SHIPPING_LINES,
  rates: URL_CONSTANTS.RULE_ENGINE.RATES,
  "approval-rules": URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULES,
};

const byIdPath = (resource: RuleEngineResourceSlug, id: string): string => {
  switch (resource) {
    case "cargo-types":
      return URL_CONSTANTS.RULE_ENGINE.CARGO_TYPE_BY_ID(id);
    case "container-types":
      return URL_CONSTANTS.RULE_ENGINE.CONTAINER_TYPE_BY_ID(id);
    case "wagon-types":
      return URL_CONSTANTS.RULE_ENGINE.WAGON_TYPE_BY_ID(id);
    case "truck-types":
      return URL_CONSTANTS.RULE_ENGINE.TRUCK_TYPE_BY_ID(id);
    case "priority-configs":
      return URL_CONSTANTS.RULE_ENGINE.PRIORITY_CONFIG_BY_ID(id);
    case "service-types":
      return URL_CONSTANTS.RULE_ENGINE.SERVICE_TYPE_BY_ID(id);
    case "weight-limit-rules":
      return URL_CONSTANTS.RULE_ENGINE.WEIGHT_LIMIT_RULE_BY_ID(id);
    case "yards":
      return URL_CONSTANTS.RULE_ENGINE.YARD_BY_ID(id);
    case "yard-distances":
      return URL_CONSTANTS.RULE_ENGINE.YARD_DISTANCE_BY_ID(id);
    case "shipping-lines":
      return URL_CONSTANTS.RULE_ENGINE.SHIPPING_LINE_BY_ID(id);
    case "rates":
      return URL_CONSTANTS.RULE_ENGINE.RATE_BY_ID(id);
    case "approval-rules":
      return URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULE_BY_ID(id);
    default:
      return `${RESOURCE_BASE[resource]}/${id}`;
  }
};

const defaultMeta = (
  dataLength: number,
  page = 1,
  pageSize = 10,
): RuleEngineListMeta => {
  const totalPages = Math.max(1, Math.ceil(dataLength / pageSize));
  return {
    total: dataLength,
    page,
    pageSize,
    totalPages,
    hasNextPage: page < totalPages,
    hasPreviousPage: page > 1,
  };
};

/** Standard envelope from the shared pagination toolkit: `{ items, meta }`. */
const isItemsEnvelope = <T extends RuleEngineRecord>(
  value: unknown,
): value is RuleEngineListResult<T> =>
  Boolean(value) &&
  typeof value === "object" &&
  Array.isArray((value as { items?: unknown }).items);

/** Legacy envelope (`{ data, meta }`) — still returned by wagon-types. */
const isLegacyEnvelope = <T extends RuleEngineRecord>(
  value: unknown,
): value is { data: T[]; meta?: RuleEngineListMeta } =>
  Boolean(value) &&
  typeof value === "object" &&
  Array.isArray((value as { data?: unknown }).data);

const normalizeList = <T extends RuleEngineRecord>(
  payload: unknown,
  page = 1,
  pageSize = 10,
): RuleEngineListResult<T> => {
  const candidates: unknown[] = [payload, unwrap(payload as { data: unknown })];

  for (const body of candidates) {
    if (isItemsEnvelope<T>(body)) {
      return {
        items: body.items,
        meta: body.meta ?? defaultMeta(body.items.length, page, pageSize),
      };
    }
    if (isLegacyEnvelope<T>(body)) {
      return {
        items: body.data,
        meta: body.meta ?? defaultMeta(body.data.length, page, pageSize),
      };
    }
    if (Array.isArray(body)) {
      return {
        items: body as T[],
        meta: defaultMeta(body.length, page, pageSize),
      };
    }
  }

  return { items: [], meta: defaultMeta(0, page, pageSize) };
};

const normalizeEntity = <T extends RuleEngineRecord>(payload: unknown): T => {
  return unwrap(payload as { data: T }) as T;
};

export const ruleEngineService = {
  list: async <T extends RuleEngineRecord>(
    resource: RuleEngineResourceSlug,
    params?: RuleEngineListParams,
  ): Promise<RuleEngineListResult<T>> => {
    const page = params?.page ?? 1;
    const pageSize = params?.pageSize ?? 10;
    const response = await client.get(RESOURCE_BASE[resource], {
      params: {
        page,
        pageSize,
        search: params?.search,
        isActive: params?.isActive,
        status: params?.status,
        sortBy: params?.sortBy,
        sortOrder: params?.sortOrder,
        requiresDirectorApproval: params?.requiresDirectorApproval,
        appliesTo: params?.appliesTo,
        trigger: params?.trigger,
      },
    });
    return normalizeList<T>(response.data, page, pageSize);
  },

  /**
   * Fetch every row of a resource by walking the pages. The API caps pageSize
   * at 100, so option/dropdown consumers that used to ask for 200-500 rows in
   * one shot go through here instead of getting silently capped (or a 400).
   */
  listAll: async <T extends RuleEngineRecord>(
    resource: RuleEngineResourceSlug,
    params?: Omit<RuleEngineListParams, "page" | "pageSize">,
  ): Promise<T[]> => {
    const pageSize = 100;
    const first = await ruleEngineService.list<T>(resource, {
      ...params,
      page: 1,
      pageSize,
    });
    const items = [...first.items];
    const totalPages = first.meta.totalPages ?? 1;
    for (let page = 2; page <= totalPages; page += 1) {
      const next = await ruleEngineService.list<T>(resource, {
        ...params,
        page,
        pageSize,
      });
      items.push(...next.items);
    }
    return items;
  },

  getById: async <T extends RuleEngineRecord>(
    resource: RuleEngineResourceSlug,
    id: string,
  ): Promise<T> => {
    const response = await client.get(byIdPath(resource, id));
    return normalizeEntity<T>(response.data);
  },

  create: async <T extends RuleEngineRecord>(
    resource: RuleEngineResourceSlug,
    payload: Record<string, unknown>,
  ): Promise<T> => {
    const response = await client.post(RESOURCE_BASE[resource], payload);
    return normalizeEntity<T>(response.data);
  },

  update: async <T extends RuleEngineRecord>(
    resource: RuleEngineResourceSlug,
    id: string,
    payload: Record<string, unknown>,
  ): Promise<T> => {
    const response = await client.patch(byIdPath(resource, id), payload);
    return normalizeEntity<T>(response.data);
  },

  remove: async (
    resource: RuleEngineResourceSlug,
    id: string,
  ): Promise<void> => {
    await client.delete(byIdPath(resource, id));
  },

  reorder: async (
    resource: RuleEngineResourceSlug,
    payload: RuleEngineReorderPayload,
  ): Promise<void> => {
    await client.post(`${RESOURCE_BASE[resource]}/reorder`, payload);
  },

  moveOrder: async (
    resource: RuleEngineResourceSlug,
    id: string,
    direction: "up" | "down",
  ): Promise<void> => {
    await client.post(`${byIdPath(resource, id)}/move-order`, { direction });
  },

  submitRate: async <T extends RuleEngineRecord>(id: string): Promise<T> => {
    const response = await client.post(
      URL_CONSTANTS.RULE_ENGINE.RATE_SUBMIT(id),
    );
    return normalizeEntity<T>(response.data);
  },

  approveRate: async <T extends RuleEngineRecord>(id: string): Promise<T> => {
    const response = await client.post(
      URL_CONSTANTS.RULE_ENGINE.RATE_APPROVE(id),
    );
    return normalizeEntity<T>(response.data);
  },

  /** File a priority-rule change (create/update/delete) for approval. */
  submitPriorityRuleChange: async (
    payload: SubmitPriorityRuleChangePayload,
  ): Promise<PriorityRuleChangeRequest> => {
    const response = await client.post(PRIORITY_RULE_CHANGES_BASE, payload);
    return unwrap(response.data) as PriorityRuleChangeRequest;
  },

  listPriorityRuleChanges: async (
    status?: PriorityRuleChangeRequest["status"],
  ): Promise<PriorityRuleChangeRequest[]> => {
    const response = await client.get(PRIORITY_RULE_CHANGES_BASE, {
      params: status ? { status } : undefined,
    });
    const body = unwrap(response.data) as unknown;
    return Array.isArray(body) ? (body as PriorityRuleChangeRequest[]) : [];
  },

  approvePriorityRuleChange: async (
    id: string,
    decisionNote?: string,
  ): Promise<PriorityRuleChangeRequest> => {
    const response = await client.post(
      `${PRIORITY_RULE_CHANGES_BASE}/${id}/approve`,
      { decisionNote },
    );
    return unwrap(response.data) as PriorityRuleChangeRequest;
  },

  rejectPriorityRuleChange: async (
    id: string,
    decisionNote?: string,
  ): Promise<PriorityRuleChangeRequest> => {
    const response = await client.post(
      `${PRIORITY_RULE_CHANGES_BASE}/${id}/reject`,
      { decisionNote },
    );
    return unwrap(response.data) as PriorityRuleChangeRequest;
  },

  /** Propose a change to a LIVE rate — it stays at its current value until approved. */
  submitRateChange: async (
    payload: SubmitRateChangePayload,
  ): Promise<RateChangeRequest> => {
    const response = await client.post(RATE_CHANGES_BASE, payload);
    return unwrap(response.data) as RateChangeRequest;
  },

  listRateChanges: async (
    status?: RateChangeRequest["status"],
  ): Promise<RateChangeRequest[]> => {
    const response = await client.get(RATE_CHANGES_BASE, {
      params: status ? { status } : undefined,
    });
    const body = unwrap(response.data) as unknown;
    return Array.isArray(body) ? (body as RateChangeRequest[]) : [];
  },

  approveRateChange: async (
    id: string,
    decisionNote?: string,
  ): Promise<RateChangeRequest> => {
    const response = await client.post(`${RATE_CHANGES_BASE}/${id}/approve`, {
      decisionNote,
    });
    return unwrap(response.data) as RateChangeRequest;
  },

  rejectRateChange: async (
    id: string,
    decisionNote?: string,
  ): Promise<RateChangeRequest> => {
    const response = await client.post(`${RATE_CHANGES_BASE}/${id}/reject`, {
      decisionNote,
    });
    return unwrap(response.data) as RateChangeRequest;
  },

  /**
   * IAM position types that an approval step can require/block. Replaces the
   * old hardcoded LINE_STAFF/DIRECTOR/CEO triple — the chain is configured from
   * whatever positions IAM actually defines.
   */
  getApprovalPositionTypes: async (): Promise<ApprovalPositionType[]> => {
    const response = await client.get(
      URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULES_POSITION_TYPES,
    );
    const body = unwrap(response.data) as unknown;
    if (Array.isArray(body)) return body as ApprovalPositionType[];
    if (
      body &&
      typeof body === "object" &&
      "data" in body &&
      Array.isArray((body as { data: unknown }).data)
    ) {
      return (body as { data: ApprovalPositionType[] }).data;
    }
    return [];
  },

  getApprovalChain: async (
    requiresDirectorApproval = true,
  ): Promise<RuleEngineRecord[]> => {
    const response = await client.get(
      URL_CONSTANTS.RULE_ENGINE.APPROVAL_RULES_CHAIN,
      {
        params: { requiresDirectorApproval },
      },
    );
    const body = unwrap(response.data) as unknown;
    if (Array.isArray(body)) return body as RuleEngineRecord[];
    if (
      body &&
      typeof body === "object" &&
      "data" in body &&
      Array.isArray((body as { data: unknown }).data)
    ) {
      return (body as { data: RuleEngineRecord[] }).data;
    }
    return [];
  },
};
