import 'server-only';
import { getSessionAccessToken } from '@/lib/auth/dal';
import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch';
import type {
  PointAccount,
  PointHistoryEntry,
} from '@/lib/domain/point-account';
import type {
  PointAccountQuery,
  PointAccountSearchField,
} from '@/lib/domain/point-account-query';

/**
 * 누적포인트 관리 Repository.
 *
 * ```
 * GET /api/v1/mngr/point/stats/pagination      학생별 누적포인트 목록
 * GET /api/v1/mngr/point/stats/log/pagination  한 학생의 포인트 내역(searchUserId 필수)
 * ```
 *
 * 조회 전용이다 — 백엔드에 쓰기 엔드포인트가 없다.
 *
 * ⚠️ 백엔드 이슈 — 화면이 기획대로 동작하지 못하는 부분
 * 1. 목록 검색이 아직 걸리지 않는다. 매퍼에 검색 WHERE(`includeWhere` — `searchCondition`
 *    1=아이디·2=이름, `searchSchulNm`·`searchGradeCd`·`searchClsNo`·`searchBno`)는 생겼지만
 *    `selectPagination`·`selectPaginationCount`가 이 블록을 include하지 않아 항상 전체가
 *    나온다. 아래 파라미터는 VO·매퍼 규약대로 보내 두었으니 백엔드가 연결만 하면 프론트
 *    수정 없이 동작한다.
 * 2. 회원번호·구분(사이트) 열의 원본이 응답에 없다. 회원번호 자리는 로그인 아이디로 대신
 *    채우고 구분은 빈 값이다.
 * 3. 현재포인트는 SQL에서 `p.current_point`를 뽑지만 VO에 필드가 없어 응답에서 사라진다.
 *    `total_point = current_point + use_point` 정의를 역산해 채운다.
 * 4. 차감포인트는 개념 자체가 없다(`POINT_TYPE_CD`는 취득 `E`·사용 `S` 둘뿐). 0으로 둔다.
 */

const STATS_PATH = '/api/v1/mngr/point/stats';

/** 화면 검색 대상 → 백엔드 `searchCondition`(매퍼 규약: 1=아이디, 2=이름). */
const SEARCH_CONDITION_BY_FIELD: Record<PointAccountSearchField, string> = {
  loginId: '1',
  studentName: '2',
};

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 toPointAccount(raw: unknown): PointAccount {
  if (!isRecord(raw)) {
    throw new Error('누적포인트 응답 항목의 형식이 올바르지 않습니다.');
  }

  const total = readNumber(raw, 'totalPoint');
  const used = readNumber(raw, 'usePoint');

  return {
    id: readString(raw, 'userId'),
    // 백엔드에 회원번호가 없다 — 로그인 아이디가 그 자리를 대신한다.
    memberNo: readString(raw, 'loginId'),
    loginId: readString(raw, 'loginId'),
    studentName: readString(raw, 'userNm'),
    schoolName: readString(raw, 'schNm'),
    grade: readNumber(raw, 'grade'),
    classNo: readNumber(raw, 'clsNo'),
    studentNo: readNumber(raw, 'bno'),
    totalPoint: total,
    deductedPoint: 0,
    usedPoint: used,
    currentPoint: total - used,
    updatedAt: readString(raw, 'lastMdfcnDtStr') || readString(raw, 'frstRegDtStr'),
    siteName: '',
  };
}

function toHistoryEntry(raw: unknown, index: number): PointHistoryEntry {
  if (!isRecord(raw)) {
    throw new Error('포인트 내역 응답 항목의 형식이 올바르지 않습니다.');
  }

  const amount = readNumber(raw, 'pointAmt');
  // POINT_TYPE_CD는 취득 `E`·사용 `S`. 사용은 음수로 보여야 부호가 곧 방향이 된다(기획 PTS10300001 ②).
  const isUse = readString(raw, 'pointTypeCd').toUpperCase() === 'S';

  return {
    id: readString(raw, 'rnum') || `${index}`,
    title: readString(raw, 'pointSj'),
    point: isUse ? -Math.abs(amount) : amount,
    siteName: '',
    date: readString(raw, 'frstRegDtStr'),
  };
}

async function requestList(
  path: string,
  query: Record<string, string | number>
): Promise<{ rows: unknown[]; reportedTotalCount: number }> {
  const accessToken = await getSessionAccessToken();

  const result = await backendFetch<unknown>(path, {
    method: 'GET',
    query,
    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 { rows: [], reportedTotalCount: 0 };
  }
  if (!isRecord(data) || !Array.isArray(data.list)) {
    throw new Error('누적포인트 목록 응답의 형식이 올바르지 않습니다.');
  }

  return {
    rows: data.list,
    reportedTotalCount:
      typeof data.totalCount === 'number' ? data.totalCount : data.list.length,
  };
}

export async function fetchPointAccounts(
  query: PointAccountQuery
): Promise<{ items: PointAccount[]; totalCount: number }> {
  const keyword = query.keyword.trim();

  const { rows, reportedTotalCount } = await requestList(
    `${STATS_PATH}/pagination`,
    {
      searchSchulNm: query.schoolName,
      searchGradeCd: query.grade === null ? '' : String(query.grade),
      searchClsNo: query.classNo === null ? '' : String(query.classNo),
      searchBno: query.studentNo === null ? '' : String(query.studentNo),
      searchCondition: SEARCH_CONDITION_BY_FIELD[query.searchField],
      searchKeyword: keyword,
      pageIndex: query.page,
      recordCountPerPage: query.pageSize,
    }
  );

  const items = rows.map(toPointAccount);
  const confirmed = (query.page - 1) * query.pageSize + items.length;

  return { items, totalCount: Math.max(reportedTotalCount, confirmed) };
}

/** 상세 팝업의 포인트 내역 — `searchUserId`가 필수다. */
export async function fetchPointHistory(
  accountId: string,
  page: number,
  pageSize: number
): Promise<{ items: PointHistoryEntry[]; totalCount: number }> {
  const { rows, reportedTotalCount } = await requestList(
    `${STATS_PATH}/log/pagination`,
    {
      searchUserId: accountId,
      pageIndex: page,
      recordCountPerPage: pageSize,
    }
  );

  const items = rows.map(toHistoryEntry);
  const confirmed = (page - 1) * pageSize + items.length;

  return { items, totalCount: Math.max(reportedTotalCount, confirmed) };
}
