import 'server-only';
import { getSessionAccessToken } from '@/lib/auth/dal';
import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch';
import { buildFileImageUrl } from '@/lib/data/repositories/file-repository';
import {
  joinProductBadges,
  parseProductBadges,
  type MerchantProduct,
} from '@/lib/domain/merchant-product';
import type { MerchantProductValues } from '@/lib/domain/merchant-product-form';

/**
 * 가맹몰 메뉴 Repository — edupay-backend origin/feature/voc d784bb6 기준(2026-09-16).
 *
 * ```
 * GET    /api/v1/mngr/voc/product/list/{frcsNo}   메뉴 전체(페이징 없음) — 상세와 INNER JOIN
 * GET    /api/v1/mngr/voc/product/{frcsProductSn}  없으면 성공 봉투에 data: null (develop은 NOT_FOUND)
 * POST   /api/v1/mngr/voc/product                  @RequestBody · 가맹점만 있으면 받는다
 * PUT    /api/v1/mngr/voc/product/{frcsProductSn}  @RequestBody · 전 컬럼 덮어씀
 * DELETE /api/v1/mngr/voc/product/{frcsProductSn}  논리 삭제(DEL_YN)
 * ```
 *
 * 등록은 상세 행이 없어도 통과하지만 목록·단건·수정·삭제는 상세와 INNER JOIN이라 상세를 저장하기
 * 전에 넣은 메뉴는 보이지도 고쳐지지도 않는다 — 그래서 화면은 상세를 먼저 저장하게 한다.
 * 목록 SQL에 ORDER BY가 없어 노출 순서(낮을수록 상단)는 여기서 정렬한다.
 */

const PRODUCT_PATH = '/api/v1/mngr/voc/product';

function isRecord(value: unknown): value is Record<string, unknown> {
  return value !== null && typeof value === 'object';
}

function readString(source: Record<string, unknown>, key: string): string | null {
  const value = source[key];
  if (typeof value === 'string' && value !== '') return value;
  if (typeof value === 'number') return String(value);
  return null;
}

function readNumber(source: Record<string, unknown>, key: string): number | null {
  const value = source[key];
  if (typeof value === 'number' && Number.isFinite(value)) return value;
  if (typeof value === 'string' && value.trim() !== '') {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : null;
  }
  return null;
}

function toProduct(raw: unknown): MerchantProduct[] {
  if (!isRecord(raw)) return [];
  const id = readString(raw, 'frcsProductSn');
  const frcsNo = readString(raw, 'frcsNo');
  if (!id || !frcsNo) return [];

  return [
    {
      id,
      frcsNo,
      name: readString(raw, 'frcsProductSj') ?? '',
      price: readNumber(raw, 'frcsProductPrice'),
      description: readString(raw, 'frcsProductCn'),
      badges: parseProductBadges(readString(raw, 'frcsBadge')),
      imageFileId: readString(raw, 'atchFileId'),
      imageUrl: buildFileImageUrl(readString(raw, 'atchFileId')),
      sortOrder: readNumber(raw, 'sortOrdr'),
      isVisible: (readString(raw, 'useYn') ?? 'Y').toUpperCase() !== 'N',
    },
  ];
}

export async function fetchMerchantProducts(frcsNo: string): Promise<MerchantProduct[]> {
  const accessToken = await getSessionAccessToken();

  const result = await backendFetch<unknown>(
    `${PRODUCT_PATH}/list/${encodeURIComponent(frcsNo)}`,
    {
      method: 'GET',
      accessToken: accessToken ?? undefined,
      cache: 'no-store',
      canHaveNullData: true,
    }
  );

  if (!result.ok) {
    throw new BackendRequestError(result);
  }
  const products = Array.isArray(result.data) ? result.data.flatMap(toProduct) : [];
  return products.sort(compareProductOrder);
}

/** 노출 순서 오름차순, 순서가 없는 메뉴는 뒤로, 같으면 등록된 순(번호)대로. */
function compareProductOrder(a: MerchantProduct, b: MerchantProduct): number {
  const orderA = a.sortOrder ?? Number.POSITIVE_INFINITY;
  const orderB = b.sortOrder ?? Number.POSITIVE_INFINITY;
  if (orderA !== orderB) return orderA - orderB;
  return a.id.localeCompare(b.id, undefined, { numeric: true });
}

export async function fetchMerchantProduct(
  productSn: string
): Promise<MerchantProduct | null> {
  const accessToken = await getSessionAccessToken();

  const result = await backendFetch<unknown>(
    `${PRODUCT_PATH}/${encodeURIComponent(productSn)}`,
    {
      method: 'GET',
      accessToken: accessToken ?? undefined,
      cache: 'no-store',
      canHaveNullData: true,
    }
  );

  if (!result.ok) {
    // 구 버전은 없는 메뉴가 404 봉투였다. 지금은 성공 봉투에 data: null이라 아래에서 null이 된다.
    if (result.code === 404) return null;
    throw new BackendRequestError(result);
  }
  return toProduct(result.data)[0] ?? null;
}

function toWritePayload(values: MerchantProductValues): Record<string, unknown> {
  return {
    frcsNo: values.frcsNo,
    frcsProductSj: values.name,
    frcsProductPrice: values.price,
    frcsProductCn: values.description,
    frcsBadge: joinProductBadges(values.badges),
    atchFileId: values.imageFileId || null,
    sortOrdr: values.sortOrder,
    useYn: values.isVisible ? 'Y' : 'N',
  };
}

async function sendWrite(
  path: string,
  method: 'POST' | 'PUT' | 'DELETE',
  body?: unknown
): Promise<void> {
  const accessToken = await getSessionAccessToken();

  const result = await backendFetch<unknown>(path, {
    method,
    ...(body === undefined ? {} : { body }),
    accessToken: accessToken ?? undefined,
    cache: 'no-store',
    canHaveNullData: true,
    canHaveEmptyBody: true,
  });

  if (!result.ok) {
    throw new BackendRequestError(result);
  }
}

export async function createMerchantProduct(
  values: MerchantProductValues
): Promise<void> {
  await sendWrite(PRODUCT_PATH, 'POST', toWritePayload(values));
}

export async function updateMerchantProduct(
  productSn: string,
  values: MerchantProductValues
): Promise<void> {
  await sendWrite(
    `${PRODUCT_PATH}/${encodeURIComponent(productSn)}`,
    'PUT',
    toWritePayload(values)
  );
}

export async function deleteMerchantProduct(productSn: string): Promise<void> {
  await sendWrite(`${PRODUCT_PATH}/${encodeURIComponent(productSn)}`, 'DELETE');
}
