import { sleep } from '@/utils';
import { ApiError } from '@/lib/api/errors';
import type { PageParams, Paginated } from '@/lib/api/types';
import { resolveSeedCity, resolveSeedDistrict } from '@/services/geography/apis/seed';
import { ADDRESSES_PAGE_SIZE } from '../constants';
import type { CreateAddressInput, CustomerAddress, AddressesApi } from '../types';

const MOCK_LATENCY_MS = 350;

// In-memory store, seeded **empty** so a session can demo both the add flow and the E-style
// empty state. The single-primary invariant is enforced here exactly as the server's filtered
// unique index does: exactly one primary while ≥1 address exists.
let store: CustomerAddress[] = [];
let nextId = 1;

// Denormalise the chosen region ids into the display names the DTO carries (the server joins
// these). A whole-city address (no district) keeps null district names.
function build(id: number, input: CreateAddressInput, isPrimary: boolean): CustomerAddress {
  const city = resolveSeedCity(input.cityId);
  const district = input.districtId == null ? undefined : resolveSeedDistrict(input.districtId);
  return {
    id,
    title: input.title,
    provinceId: input.provinceId,
    cityId: input.cityId,
    cityNameFa: city?.nameFa ?? '',
    cityNameEn: city?.nameEn ?? '',
    districtId: input.districtId ?? null,
    districtNameFa: district?.nameFa ?? null,
    districtNameEn: district?.nameEn ?? null,
    addressLine: input.addressLine,
    postalCode: input.postalCode ?? null,
    latitude: input.latitude ?? null,
    longitude: input.longitude ?? null,
    isPrimary,
    recipientName: input.recipientName ?? null,
    recipientPhone: input.recipientPhone ?? null,
  };
}

const clearPrimaryExcept = (id: number) =>
  store.map((address) => (address.id === id ? address : { ...address, isPrimary: false }));

// Primary first, then most-recent — mirrors the contract's "primary first" list order.
const ordered = () => [...store].sort((a, b) => Number(b.isPrimary) - Number(a.isPrimary) || b.id - a.id);

/**
 * In-memory mock behind the AddressesApi seam — the b4 endpoints exist, but the wire DTO lacks
 * `provinceId` (REQ-009) and the create body geocodes instead of accepting the pin (REQ-008),
 * so this drives the UI until those land. Mirrors the real shapes for a one-line swap.
 */
export const addressesMockApi: AddressesApi = {
  list: async (params?: PageParams): Promise<Paginated<CustomerAddress>> => {
    await sleep(MOCK_LATENCY_MS);
    const all = ordered();
    const page = params?.page ?? 1;
    const pageSize = params?.pageSize ?? ADDRESSES_PAGE_SIZE;
    const start = (page - 1) * pageSize;
    return { items: all.slice(start, start + pageSize), total: all.length, page, pageSize };
  },

  create: async (input: CreateAddressInput): Promise<CustomerAddress> => {
    await sleep(MOCK_LATENCY_MS);
    const becomesPrimary = store.length === 0 || input.isPrimary === true;
    const address = build(nextId++, input, becomesPrimary);
    store = becomesPrimary ? [address, ...clearPrimaryExcept(address.id)] : [address, ...store];
    return address;
  },

  update: async (id: number, input: CreateAddressInput): Promise<CustomerAddress> => {
    await sleep(MOCK_LATENCY_MS);
    const existing = store.find((address) => address.id === id);
    if (!existing) throw new ApiError(404, 'Address not found');
    // Editing never removes the sole primary: keep the prior flag unless the form promotes it.
    const isPrimary = input.isPrimary === true || existing.isPrimary;
    const updated = build(id, input, isPrimary);
    store = store.map((address) => (address.id === id ? updated : address));
    if (isPrimary) store = clearPrimaryExcept(id);
    return updated;
  },

  setPrimary: async (id: number): Promise<void> => {
    await sleep(MOCK_LATENCY_MS);
    if (!store.some((address) => address.id === id)) throw new ApiError(404, 'Address not found');
    store = clearPrimaryExcept(id).map((address) => (address.id === id ? { ...address, isPrimary: true } : address));
  },

  remove: async (id: number): Promise<void> => {
    await sleep(MOCK_LATENCY_MS);
    const removed = store.find((address) => address.id === id);
    store = store.filter((address) => address.id !== id);
    // Keep exactly one primary: if the deleted row was primary, promote the next in order.
    if (removed?.isPrimary && store.length > 0) {
      const next = ordered()[0].id;
      store = store.map((address) => ({ ...address, isPrimary: address.id === next }));
    }
  },
};
