'use server';

import { revalidatePath } from 'next/cache';
import { verifySession } from '@/lib/auth/dal';
import {
  createDecorationItem,
  deleteDecorationItem,
  isDecorationItemIdTaken,
  updateDecorationItem,
} from '@/lib/data/repositories/decoration-item-repository';
import type {
  DecorationItemCategory,
  DecorationItemType,
} from '@/lib/domain/decoration-item';
import {
  validateDecorationItemCreate,
  validateDecorationItemUpdate,
  type DecorationItemEditableValues,
  type DecorationItemFormState,
} from '@/lib/domain/decoration-item-form';
import { DECORATION_ITEMS_PATH } from '@/lib/domain/decoration-item-query';

/**
 * 꾸미기 아이템 등록/수정/삭제 Server Action.
 *
 * **모든 Action이 `verifySession()`으로 시작한다** — Server Action은 UI를 거치지 않고 직접
 * POST될 수 있어 이 확인이 유일한 최종 방어선이다(설계서 §3 SRP 체크).
 *
 * 검증은 화면이 아니라 여기서 확정한다(`lib/domain/decoration-item-form.ts`의 규칙을 호출) —
 * 화면의 required 속성·기본값 채움은 편의일 뿐 신뢰 경계가 아니다.
 *
 * 실제 저장은 Repository에 맡긴다. 백엔드에 이 도메인의 API가 전혀 없어(조회조차 없음, 사용자
 * 확정 사항) Repository가 mock 저장소로 위임하고 있지만, **이 파일은 그 사실을 알지 못한다** —
 * 백엔드 API가 생겨도 이 파일은 바뀌지 않는다.
 *
 * `DecorationItemFormState` 타입과 그 초깃값(`INITIAL_DECORATION_ITEM_FORM_STATE`)은 이
 * 파일이 아니라 `lib/domain/decoration-item-form.ts`에 있다 — Next.js가 `'use server'`
 * 파일에서 함수가 아닌 값(일반 객체 상수)을 export하는 것을 런타임에 거부하기 때문이다
 * (`decoration-item-form.ts`의 해당 타입 주석 참조). 타입만 이 파일에서 다시 쓰는 것은
 * 문제 없다 — 타입은 컴파일 시 지워져 런타임 export로 남지 않는다.
 */

const INVALID_REQUEST_MESSAGE = '요청이 올바르지 않습니다.';
const DUPLICATE_ITEM_ID_MESSAGE = '이미 사용 중인 아이템 ID입니다.';

function readString(formData: FormData, key: string): string {
  const value = formData.get(key);
  return typeof value === 'string' ? value : '';
}

/**
 * 폼 숫자 입력을 파싱한다. `Number('')`이 조용히 `0`이 되는 JS 함정을 피하려고 빈 문자열은
 * 명시적으로 `NaN`으로 취급한다 — 그래야 `Number.isInteger` 검증이 "값이 비었음"을 정상적으로
 * 잡아낸다(포인트 0은 유효한 값이라 "비어서 0"과 "실제로 0"을 구분해야 한다).
 */
function parseFormNumber(formData: FormData, key: string): number {
  const trimmed = readString(formData, key).trim();
  return trimmed === '' ? NaN : Number(trimmed);
}

/**
 * 등록·수정이 공유하는 입력 항목을 읽는다. 유형·카테고리는 select/radio 값을 그대로 읽고,
 * 허용 목록을 벗어난 값(위조된 요청 포함)은 뒤이은 `validateEditableValues`가 걸러낸다 — 여기서
 * 하는 캐스팅은 타입을 맞추는 것일 뿐 신뢰를 부여하지 않는다.
 */
function readEditableValues(formData: FormData): DecorationItemEditableValues {
  return {
    itemType: readString(formData, 'itemType') as DecorationItemType,
    name: readString(formData, 'name'),
    category: readString(formData, 'category') as DecorationItemCategory,
    points: parseFormNumber(formData, 'points'),
    description: readString(formData, 'description'),
    isActive: readString(formData, 'isActive') === 'true',
    sortOrder: parseFormNumber(formData, 'sortOrder'),
  };
}

/** 시안 ADM_ITM_102_p — 꾸미기 아이템 등록. */
export async function createDecorationItemAction(
  _prevState: DecorationItemFormState,
  formData: FormData
): Promise<DecorationItemFormState> {
  await verifySession();

  const validation = validateDecorationItemCreate({
    ...readEditableValues(formData),
    itemId: readString(formData, 'itemId'),
  });

  if (!validation.ok) {
    return { status: 'error', errors: validation.errors };
  }

  const { itemId, ...editable } = validation.values;

  // 시안에는 admins의 [중복확인] 같은 버튼이 없지만, 아이템ID가 유일 식별자이자 수정/삭제
  // 대상 키라 저장 직전에 반드시 다시 확인한다(decoration-item-repository.ts 주석 참조).
  if (await isDecorationItemIdTaken(itemId)) {
    return { status: 'error', errors: { itemId: DUPLICATE_ITEM_ID_MESSAGE } };
  }

  await createDecorationItem({ itemId, ...editable });

  revalidatePath(DECORATION_ITEMS_PATH);
  return { status: 'success' };
}

/** 시안 ADM_ITM_103_p — 꾸미기 아이템 수정. 아이템ID는 읽기 전용이라 변경 대상이 아니다. */
export async function updateDecorationItemAction(
  _prevState: DecorationItemFormState,
  formData: FormData
): Promise<DecorationItemFormState> {
  await verifySession();

  const itemId = readString(formData, 'itemId');
  if (!itemId) {
    return { status: 'error', message: INVALID_REQUEST_MESSAGE };
  }

  const validation = validateDecorationItemUpdate(readEditableValues(formData));
  if (!validation.ok) {
    return { status: 'error', errors: validation.errors };
  }

  await updateDecorationItem(itemId, validation.values);

  revalidatePath(DECORATION_ITEMS_PATH);
  return { status: 'success' };
}

/**
 * 시안 ADM_ITM_101 — 삭제. 확인 얼럿은 화면(`decoration-item-row-actions.tsx`)이 띄우고,
 * 여기서는 인증과 입력만 확인한다.
 *
 * 폼 제출이 아니라 얼럿의 [삭제] 클릭에 반응하는 단발 호출이라 `useActionState`의
 * (prevState, formData) 규약 대신 id를 직접 받는다(admins의 `deleteAdminMemberAction`과 동일한
 * 이유).
 */
export async function deleteDecorationItemAction(
  itemId: string
): Promise<DecorationItemFormState> {
  await verifySession();

  if (!itemId) {
    return { status: 'error', message: INVALID_REQUEST_MESSAGE };
  }

  await deleteDecorationItem(itemId);

  revalidatePath(DECORATION_ITEMS_PATH);
  return { status: 'success' };
}
