import 'server-only';
import { getSessionAccessToken } from '@/lib/auth/dal';
import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch';
import type { PointStandard } from '@/lib/domain/point-standard';
import type { PointStandardQuery } from '@/lib/domain/point-standard-query';
import type { PointStandardValues } from '@/lib/domain/point-standard-form';

/**
 * 포인트 지급기준(포인트 정책) Repository — 백엔드 `TB_COM_EVENT_POINT`.
 *
 * ```
 * GET    /api/v1/mngr/point/policy/pagination      목록
 * POST   /api/v1/mngr/point/policy                 어노테이션 없음 → form
 * PUT    /api/v1/mngr/point/policy/{eventPtSn}     @RequestBody(JSON)
 * DELETE /api/v1/mngr/point/policy/{eventPtSn}     soft delete(DEL_YN)
 * ```
 *
 * 등록은 form, 수정은 JSON으로 갈린다 — 콘텐츠·퀴즈와 같은 규약이다.
 *
 * 이슈: 경로가 `/mngr/point`에서 `/mngr/point/policy`로 옮겨졌다(2026-08-21, 누적포인트
 *   `/point/stats`가 생기면서 갈라졌다). 옮기기 전 경로로 부르면 404다.
 *
 * ⚠️ 백엔드 결함(보고함, 미수정)
 * 1. **`MngrPointPolicyVo`에 `eventPtSn` 말고는 필드가 없다.** 목록 SQL은 EVENT_CD·
 *    GRANT_POINT_AMT·DAY_LMT_POINT_AMT·DUPL_YN·EVENT_EXPLN·USE_YN을 select하지만 담을 속성이
 *    없어 조용히 버려진다 — 지금 목록은 번호와 등록일만 온다. 아래 매핑은 필드가 채워지면
 *    바로 동작하도록 미리 맞춰 두었다.
 * 2. **등록이 실패한다.** 같은 이유로 INSERT의 `#{eventCd}` 등이 VO에 없어 MyBatis가
 *    `There is no getter for property named 'eventCd'`로 끊는다. INSERT 컬럼 목록에 PK인
 *    `EVENT_PT_SN`을 넣고 `#{eventPtSn}`(항상 null)을 바인딩하는 것도 함께 봐야 한다.
 * 3. **수정·삭제가 항상 실패한다.** `MngrPointPolicyServiceImpl.selectOne`이 `return null;`
 *    스텁이라 컨트롤러가 늘 "포인트 정책 조회에 실패했습니다"로 끊는다.
 * 4. `selectOne` SQL은 `includeSql`이 이미 `WHERE`를 갖고 있는데 뒤에 `WHERE`를 또 붙이고,
 *    없는 별칭 `m.`을 쓴다 — 스텁이 걷히면 이 SQL도 함께 고쳐야 한다.
 * 5. 목록에 검색 조건이 없다(`MngrPointPolicySearchVo`가 비어 있다). 화면의 검색어는 받아온
 *    뒤 여기서 거른다.
 */

const POINT_PATH = '/api/v1/mngr/point/policy';

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

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

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

function toPointStandard(raw: unknown): PointStandard {
  if (!isRecord(raw)) {
    throw new Error('포인트 정책 응답 항목의 형식이 올바르지 않습니다.');
  }

  const id = readString(raw, 'eventPtSn');
  if (!id) {
    throw new Error('포인트 정책 응답에 eventPtSn이 없습니다.');
  }

  return {
    id,
    eventCode: readString(raw, 'eventCd'),
    point: readNumber(raw, 'grantPointAmt'),
    limitPoint: readNumber(raw, 'dayLmtPointAmt'),
    isDuplicable: readString(raw, 'duplYn') !== 'N',
    description: readString(raw, 'eventExpln'),
    isActive: readString(raw, 'useYn') !== 'N',
    updatedAt:
      readString(raw, 'lastMdfcnDtStr') || readString(raw, 'frstRegDtStr'),
  };
}

async function requestList(
  pageIndex: number,
  recordCountPerPage: number
): Promise<{ items: PointStandard[]; reportedTotalCount: number }> {
  const accessToken = await getSessionAccessToken();

  const result = await backendFetch<unknown>(`${POINT_PATH}/pagination`, {
    method: 'GET',
    query: { pageIndex, recordCountPerPage },
    accessToken: accessToken ?? undefined,
    cache: 'no-store',
    canHaveNullData: true,
    canHaveEmptyBody: true,
  });

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

  const data = result.data;
  if (data === null || data === undefined) {
    return { items: [], reportedTotalCount: 0 };
  }
  if (!isRecord(data) || !Array.isArray(data.list)) {
    throw new Error('포인트 정책 목록 응답의 형식이 올바르지 않습니다.');
  }

  return {
    items: data.list.map(toPointStandard),
    reportedTotalCount:
      typeof data.totalCount === 'number' ? data.totalCount : data.list.length,
  };
}

function matchesKeyword(standard: PointStandard, keyword: string): boolean {
  const needle = keyword.trim().toLowerCase();
  return (
    standard.eventCode.toLowerCase().includes(needle) ||
    standard.description.toLowerCase().includes(needle)
  );
}

/** 검색어가 없으면 백엔드 페이징을 그대로 쓰고, 있으면 전체를 받아 여기서 거른다(결함 5). */
const KEYWORD_SCAN_LIMIT = 500;

export async function fetchPointStandards(
  query: PointStandardQuery
): Promise<{ items: PointStandard[]; totalCount: number }> {
  if (!query.keyword.trim()) {
    const { items, reportedTotalCount } = await requestList(
      query.page,
      query.pageSize
    );
    const confirmed = (query.page - 1) * query.pageSize + items.length;
    return { items, totalCount: Math.max(reportedTotalCount, confirmed) };
  }

  const { items } = await requestList(1, KEYWORD_SCAN_LIMIT);
  const filtered = items.filter((item) => matchesKeyword(item, query.keyword));
  const start = (query.page - 1) * query.pageSize;

  return {
    items: filtered.slice(start, start + query.pageSize),
    totalCount: filtered.length,
  };
}

type PointStandardPayload = {
  eventCd: string;
  grantPointAmt: string;
  dayLmtPointAmt: string;
  duplYn: string;
  eventExpln: string;
  useYn: string;
};

function buildPayload(values: PointStandardValues): PointStandardPayload {
  return {
    eventCd: values.eventCode,
    grantPointAmt: String(values.point),
    dayLmtPointAmt: String(values.limitPoint),
    duplYn: values.isDuplicable ? 'Y' : 'N',
    eventExpln: values.description,
    useYn: values.isActive ? 'Y' : 'N',
  };
}

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

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

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

/**
 * 등록은 `@RequestBody`가 없어 form으로 간다.
 *
 * TODO: 백엔드가 `insert`에 `@RequestBody`를 붙이면 수정과 같은 JSON으로 통일한다. 반영 여부는
 * `/v3/api-docs`에서 이 경로의 POST에 `requestBody`가 생겼는지로 확인한다.
 */
export async function createPointStandard(
  values: PointStandardValues
): Promise<void> {
  await sendWrite(POINT_PATH, 'POST', undefined, { ...buildPayload(values) });
}

export async function updatePointStandard(
  values: PointStandardValues
): Promise<void> {
  await sendWrite(
    `${POINT_PATH}/${encodeURIComponent(values.id)}`,
    'PUT',
    buildPayload(values)
  );
}

export async function deletePointStandard(id: string): Promise<void> {
  await sendWrite(`${POINT_PATH}/${encodeURIComponent(id)}`, 'DELETE');
}
