'use server';

import { revalidatePath } from 'next/cache';
import { verifySession } from '@/lib/auth/dal';
import {
  createAdminMember,
  deleteAdminMember,
  isAdminLoginIdTaken,
  updateAdminMember,
} from '@/lib/data/repositories/admin-member-repository';
import { ADMIN_MENU_OPTIONS } from '@/lib/domain/admin-member';
import {
  joinPhoneNumber,
  validateAdminLoginId,
  validateAdminMemberCreate,
  validateAdminMemberUpdate,
  type AdminMemberEditableValues,
  type AdminMemberFormErrors,
} from '@/lib/domain/admin-member-form';
import { ADMIN_MEMBERS_PATH } from '@/lib/domain/admin-member-query';

/**
 * 관리자 회원 등록/수정/삭제 Server Action.
 *
 * **모든 Action이 `verifySession()`으로 시작한다** — Server Action은 UI를 거치지 않고 직접
 * POST될 수 있어 이 확인이 유일한 최종 방어선이다(설계서 §8).
 *
 * 검증은 화면이 아니라 여기서 확정한다(`lib/domain/admin-member-form.ts`의 규칙을 호출) —
 * 화면의 required 속성은 편의일 뿐 신뢰 경계가 아니다.
 *
 * 실제 저장은 Repository에 맡긴다. 백엔드에 등록/수정/삭제 API가 아직 없어 Repository가 mock
 * 저장소로 위임하고 있지만, **이 파일은 그 사실을 알지 못한다** — 백엔드 API가 생겨도 이 파일은
 * 바뀌지 않는다.
 */

export type AdminMemberFormState =
  | { status: 'idle' }
  | { status: 'error'; message?: string; errors?: AdminMemberFormErrors }
  | { status: 'success' };

export const INITIAL_ADMIN_MEMBER_FORM_STATE: AdminMemberFormState = {
  status: 'idle',
};

const INVALID_REQUEST_MESSAGE = '요청이 올바르지 않습니다.';
const SELF_DELETE_MESSAGE = '현재 로그인한 본인 계정은 삭제할 수 없습니다.';
const DUPLICATE_LOGIN_ID_MESSAGE = '이미 사용 중인 ID입니다.';

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

/** 체크박스처럼 같은 이름으로 여러 번 오는 값 — 허용 목록에 있는 것만 남긴다. */
function readMenuCodes(formData: FormData): string[] {
  return formData
    .getAll('menuCodes')
    .filter((value): value is string => typeof value === 'string')
    .filter((value) =>
      ADMIN_MENU_OPTIONS.some((option) => option.value === value)
    );
}

/**
 * 등록·수정이 공유하는 입력 항목을 읽는다. 휴대전화번호는 시안대로 3칸으로 나뉘어 오므로 여기서
 * 하나의 값으로 합친다 — 형식이 어긋나면 빈 문자열이 되어 검증에서 걸린다.
 */
function readEditableValues(formData: FormData): AdminMemberEditableValues {
  const phoneNumber = joinPhoneNumber([
    readString(formData, 'phoneNumber1'),
    readString(formData, 'phoneNumber2'),
    readString(formData, 'phoneNumber3'),
  ]);

  return {
    password: readString(formData, 'password'),
    phoneNumber: phoneNumber ?? '',
    email: readString(formData, 'email'),
    roleCode: readString(formData, 'roleCode'),
    menuCodes: readMenuCodes(formData),
  };
}

/** 시안 ADM_ADM_102_p — 관리자 등록. */
export async function createAdminMemberAction(
  _prevState: AdminMemberFormState,
  formData: FormData
): Promise<AdminMemberFormState> {
  await verifySession();

  const validation = validateAdminMemberCreate({
    ...readEditableValues(formData),
    name: readString(formData, 'name'),
    loginId: readString(formData, 'loginId'),
  });

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

  const { name, loginId, phoneNumber, email, roleCode, menuCodes } =
    validation.values;

  // 화면의 [중복 확인]은 편의 기능일 뿐이라 저장 직전에 다시 확인한다 — 확인을 누르지 않고
  // 직접 제출하는 경로가 열려 있고, 확인 후 저장까지의 사이에 선점될 수도 있다.
  if (await isAdminLoginIdTaken(loginId)) {
    return { status: 'error', errors: { loginId: DUPLICATE_LOGIN_ID_MESSAGE } };
  }

  // 비밀번호는 형식만 검증하고 값은 넘기지 않는다 — 저장할 백엔드 API가 아직 없고, mock
  // 저장소에 평문 비밀번호를 보관하지 않기 때문이다(`admin-member-store.ts` 주석 참조).
  await createAdminMember({
    name,
    loginId,
    phoneNumber,
    email,
    roleCode,
    menuCodes,
  });

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

/** 시안 ADM_ADM_103_p — 관리자 수정. 이름·ID는 읽기 전용이라 변경 대상이 아니다. */
export async function updateAdminMemberAction(
  _prevState: AdminMemberFormState,
  formData: FormData
): Promise<AdminMemberFormState> {
  await verifySession();

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

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

  const { phoneNumber, email, roleCode, menuCodes } = validation.values;

  await updateAdminMember(id, {
    phoneNumber,
    email,
    roleCode,
    menuCodes,
    // 시안 ③의 "잠김여부"는 활성/비활성으로 표기되고 활성이 곧 잠기지 않은 상태다.
    isLocked: readString(formData, 'isLocked') === 'true',
  });

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

/**
 * 시안 ADM_ADM_101 ⑤ — 삭제. 확인 얼럿은 화면이 띄우고, 여기서는 권한과 자기 계정만 확인한다.
 *
 * 폼 제출이 아니라 얼럿의 [삭제] 클릭에 반응하는 단발 호출이라 `useActionState`의
 * (prevState, formData) 규약 대신 id를 직접 받는다.
 */
export async function deleteAdminMemberAction(
  id: string
): Promise<AdminMemberFormState> {
  const admin = await verifySession();

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

  // 시안의 "최고관리자 본인 계정 삭제 방지". 세션의 adminId는 백엔드 accessToken의 `adminId`
  // 클레임이고 그 값이 곧 목록의 `admUserId`라(EgovJwtTokenUtil.generateAccessToken) 그대로
  // 비교할 수 있다. 역할과 무관하게 "로그인한 본인"을 막는다 — 자기 계정을 지워 스스로
  // 로그인 불가 상태가 되는 것은 어떤 역할이든 사고이기 때문이다.
  if (id === admin.id) {
    return { status: 'error', message: SELF_DELETE_MESSAGE };
  }

  await deleteAdminMember(id);

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

export type LoginIdCheckResult =
  | { status: 'idle' }
  | { status: 'available'; loginId: string }
  | { status: 'unavailable'; message: string };

/**
 * 시안 ADM_ADM_102_p ① — ID 중복 확인.
 *
 * 폼 제출이 아니라 버튼 클릭에 반응하는 단발 호출이라 `useActionState`의 (prevState, formData)
 * 규약 대신 값을 직접 받는다 — 등록 폼 안에 또 다른 폼을 중첩할 수 없기 때문이다(HTML 제약).
 * 클라이언트에서 일반 함수처럼 `await` 한다.
 *
 * 형식이 맞지 않는 ID는 중복 여부를 물을 필요도 없이 되돌린다. 확인에 성공하면 **검사한 ID를 함께**
 * 돌려주는데, 화면이 "확인한 ID"와 "지금 입력창의 ID"를 비교해 확인 후 값을 고친 경우를 잡아내기
 * 위해서다.
 */
export async function checkAdminLoginId(
  loginId: string
): Promise<LoginIdCheckResult> {
  await verifySession();

  const normalized = loginId.trim();

  const formatError = validateAdminLoginId(normalized);
  if (formatError) {
    return { status: 'unavailable', message: formatError };
  }

  if (await isAdminLoginIdTaken(normalized)) {
    return { status: 'unavailable', message: DUPLICATE_LOGIN_ID_MESSAGE };
  }

  return { status: 'available', loginId: normalized };
}
