import 'server-only';
import { cache } from 'react';
import { getSessionAccessToken } from '@/lib/auth/dal';
import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch';
import type {
  CommonCode,
  CommonCodeDetail,
  CommonCodeGroup,
} from '@/lib/domain/common-code';
import type {
  CommonCodeDetailValues,
  CommonCodeGroupValues,
} from '@/lib/domain/common-code-form';

/**
 * 공통코드 Repository — 그룹(`TB_SYS_COM_CD`)과 상세(`TB_SYS_COM_CD_DTL`) 두 층을 모두 맡는다.
 *
 * ```
 * GET    /api/v1/mngr/code/list                  그룹 목록 (ROLE_ADMIN)
 * GET    /api/v1/mngr/code/list/{comCd}          그룹의 상세코드 목록
 * POST   /api/v1/mngr/code                       그룹 등록 — @RequestBody(JSON)
 * PUT    /api/v1/mngr/code/{comCd}               그룹 수정 — @RequestBody(JSON)
 * DELETE /api/v1/mngr/code/{comCd}               그룹 삭제 — soft delete(DEL_YN)
 * POST   /api/v1/mngr/code/detail                상세 등록 — 어노테이션 없음 → form
 * PUT    /api/v1/mngr/code/{comCd}/{comDtlCd}    상세 수정 — @RequestBody(JSON)
 * DELETE /api/v1/mngr/code/{comCd}/{comDtlCd}    상세 삭제 — soft delete
 * ```
 *
 * 백엔드(edupay-backend, develop)의 MngrCodeApiController / MngrCodeMapper.xml을 읽고 확인한 것:
 *
 * - **본문 형식이 엔드포인트마다 다르다.** 상세 등록만 `@ParameterObject`(form)이고 나머지 쓰기는
 *   전부 `@RequestBody`(JSON)다 — 한쪽으로 통일해 보내면 반대쪽이 조용히 깨진다.
 * - **두 목록 모두 페이징이 없다**(전체 반환). 그래서 총건수는 정확하고, 자르는 일은 호출부가 한다.
 * - **그룹 검색은 완전일치다**(`com_cd = #{}` / `cd_nm = #{}`). 시안은 부분검색이라 검색 파라미터를
 *   쓰지 않고 전체를 받아 호출부가 거른다(사용자 확정 사항).
 * - **정렬은 고정이다** — 두 목록 다 `ROW_NUMBER() OVER (...)`를 `ORDER BY RNUM DESC`로 뒤집는
 *   이중 역순이라 결과는 등록일 오름차순이다. 화면에서 다시 정렬하지 않는다.
 * - **삭제는 soft delete**이고 조회가 `DEL_YN != 'Y'`로 거른다.
 *
 * ⚠️ **백엔드 결함(보고함, 미수정)**
 * 1. 상세 목록 조회 SQL이 `DTL_CD_EXPLN`을 select하지 않는다 → 상세코드설명이 늘 비어 온다.
 * 2. 상세 등록 INSERT에도 `DTL_CD_EXPLN`이 없다 → 등록 시 입력한 설명이 저장되지 않는다.
 *    (수정 UPDATE에는 있어 수정으로는 저장된다 — 다만 위 1 때문에 목록에서는 여전히 안 보인다.)
 * 사용자 지시로 시안대로 화면을 두고 백엔드에 수정을 요청한다 — 고쳐지면 프론트 수정 없이 동작한다.
 *
 * 캐시: 조회는 `no-store` — 관리 화면이라 신선도가 우선이다. `cache()`는 한 요청 안의 중복 호출만
 * 막는다(화면이 그리기용으로, Server Action이 검증용으로 같은 목록을 부른다).
 */

const CODE_PATH = '/api/v1/mngr/code';

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];
  return typeof value === 'string' && value.length > 0 ? value : null;
}

async function requestList(path: string): Promise<unknown[]> {
  const accessToken = await getSessionAccessToken();

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

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

  if (!Array.isArray(result.data)) {
    throw new Error('공통코드 응답의 형식이 올바르지 않습니다.');
  }

  return result.data;
}

/**
 * 응답 1건 → 도메인 타입. 코드값이 없는 행은 **예외 대신 건너뛴다** — 코드 한 줄이 깨졌다고
 * 화면 전체를 못 쓰게 만들 이유가 없다(목록 조회의 fail-fast와 다른 판단이다).
 */
function toGroup(raw: unknown): CommonCodeGroup[] {
  if (!isRecord(raw)) {
    return [];
  }

  const comCd = readString(raw, 'comCd');
  if (!comCd) {
    return [];
  }

  return [
    {
      comCd,
      name: readString(raw, 'cdNm') ?? '',
      description: readString(raw, 'cdExpln'),
      createdAt: readString(raw, 'frstRegDtStr'),
    },
  ];
}

function toDetail(raw: unknown): CommonCodeDetail[] {
  if (!isRecord(raw)) {
    return [];
  }

  const comDtlCd = readString(raw, 'comDtlCd');
  if (!comDtlCd) {
    return [];
  }

  const sortSeq = raw.sortSeq;

  return [
    {
      comCd: readString(raw, 'comCd') ?? '',
      comDtlCd,
      name: readString(raw, 'cdNm') ?? '',
      // 지금은 백엔드가 내려 주지 않아 늘 null이다(파일 상단 결함 1).
      description: readString(raw, 'dtlCdExpln'),
      sortSeq: typeof sortSeq === 'number' ? sortSeq : 0,
      createdAt: readString(raw, 'frstRegDtStr'),
    },
  ];
}

/** 공통코드(그룹) 전체. 검색·페이징은 호출부가 한다(파일 상단 주석 참조). */
export const fetchCodeGroups = cache(async function fetchCodeGroups(): Promise<
  CommonCodeGroup[]
> {
  const rows = await requestList(`${CODE_PATH}/list`);
  return rows.flatMap(toGroup);
});

/** 한 그룹의 상세코드 전체. */
export const fetchCodeDetails = cache(async function fetchCodeDetails(
  comCd: string
): Promise<CommonCodeDetail[]> {
  const rows = await requestList(
    `${CODE_PATH}/list/${encodeURIComponent(comCd)}`
  );
  return rows.flatMap(toDetail);
});

/**
 * 다른 화면이 선택지로 쓰는 최소 표현. 상세코드 조회를 그대로 쓰되 화면이 알 필요 없는 것을
 * 덜어 낸다 — 같은 `cache()`를 타므로 한 요청 안에서 왕복이 늘지 않는다.
 */
export async function fetchCommonCodes(groupCode: string): Promise<CommonCode[]> {
  const details = await fetchCodeDetails(groupCode);
  return details.map((detail) => ({ code: detail.comDtlCd, label: detail.name }));
}

/*
 * ─── 쓰기 경로 ────────────────────────────────────────────────────────────────
 * 상세 등록만 form이고 나머지는 JSON이다(파일 상단 주석 참조).
 * 성공 응답은 모두 `ApiResponseVO.success(null)`이라 data가 정상적으로 null이다.
 */

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

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

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

export async function createCodeGroup(
  values: CommonCodeGroupValues
): Promise<void> {
  await sendWrite(CODE_PATH, 'POST', {
    body: { comCd: values.comCd, cdNm: values.name, cdExpln: values.description },
  });
}

/** 코드ID는 경로로만 간다 — 수정 대상이 아니라 대상을 가리키는 값이다. */
export async function updateCodeGroup(
  comCd: string,
  values: CommonCodeGroupValues
): Promise<void> {
  await sendWrite(`${CODE_PATH}/${encodeURIComponent(comCd)}`, 'PUT', {
    body: { cdNm: values.name, cdExpln: values.description },
  });
}

export async function deleteCodeGroup(comCd: string): Promise<void> {
  await sendWrite(`${CODE_PATH}/${encodeURIComponent(comCd)}`, 'DELETE');
}

/** 상세 등록만 form이다 — 컨트롤러가 `@RequestBody` 없이 받는다. */
export async function createCodeDetail(
  values: CommonCodeDetailValues
): Promise<void> {
  await sendWrite(`${CODE_PATH}/detail`, 'POST', {
    form: {
      comCd: values.comCd,
      comDtlCd: values.comDtlCd,
      cdNm: values.name,
      // 백엔드 INSERT가 이 값을 쓰지 않는다(파일 상단 결함 2). 고쳐지면 그대로 저장된다.
      dtlCdExpln: values.description,
      sortSeq: values.sortSeq,
    },
  });
}

export async function updateCodeDetail(
  comCd: string,
  comDtlCd: string,
  values: CommonCodeDetailValues
): Promise<void> {
  await sendWrite(
    `${CODE_PATH}/${encodeURIComponent(comCd)}/${encodeURIComponent(comDtlCd)}`,
    'PUT',
    {
      body: {
        cdNm: values.name,
        dtlCdExpln: values.description,
        sortSeq: values.sortSeq,
      },
    }
  );
}

export async function deleteCodeDetail(
  comCd: string,
  comDtlCd: string
): Promise<void> {
  await sendWrite(
    `${CODE_PATH}/${encodeURIComponent(comCd)}/${encodeURIComponent(comDtlCd)}`,
    'DELETE'
  );
}
