import 'server-only';
import type { StudentMember } from '@/lib/domain/student-member';
import type {
  StudentMemberQuery,
  StudentMemberSearchField,
} from '@/lib/domain/student-member-query';

/**
 * mock 학생 회원 Repository.
 *
 * 데이터 출처는 외부 시스템 「알콩」이며, 백엔드(edupay-backend)에 학생 회원 조회 API가 아직
 * 없어 mock으로 구현한다. 공개 시그니처(도메인 타입만 주고받음)는 백엔드 연동 후에도 유지한다 —
 * 연동 시점에는 이 파일의 내부 구현만 실제 API 호출로 교체하고, 각 함수에 캐시 전략
 * (`fetchStudentMembers`/`fetchStudentMemberById`는 `cache: 'no-store'` — 개인정보를 담은
 * 목록이고 검색 조건이 매 요청 달라지며 사용여부가 자주 바뀌므로 재사용 캐시를 두지 않는다)을
 * 명시적으로 추가해야 한다. 지금은 실제 `fetch()` 호출이 없어(in-memory mock) 옵션을 걸 대상이
 * 없다는 점에 유의 — 위 주석은 연동 시점에 적용할 의도를 남겨두는 것이다.
 *
 * mock 데이터는 결정적(deterministic)으로 생성한다 — `Math.random()`을 쓰지 않고 인덱스 기반
 * 순환으로 이름·학교·학년 등을 만들어 매 요청 동일한 128건을 반환한다. 모듈이 처음 로드될 때
 * 한 번만 생성해(module-level 배열) 이후 요청 간 같은 데이터를 유지하고,
 * `updateStudentMemberActiveStatus`가 반영한 변경도 프로세스 생존 동안 유지된다(재시작 시
 * 초기화 — 실제 영속 저장소가 아님에 유의).
 */

const TOTAL_MOCK_COUNT = 128;
const MEMBER_CODE_PREFIX = 'ST';

const SURNAMES = ['김', '이', '박', '최', '정', '강', '조', '윤', '장', '임'];
const GIVEN_NAMES = [
  '민준',
  '서연',
  '도윤',
  '지우',
  '하은',
  '주원',
  '수아',
  '지호',
  '예은',
  '건우',
  '서윤',
  '연우',
  '다은',
];
const GUARDIAN_GIVEN_NAMES = ['영수', '순자', '동현', '미경', '재훈', '은영'];

const SCHOOL_NAMES = [
  '한빛초등학교',
  '늘푸른초등학교',
  '서울중학교',
  '대한중학교',
  '한강고등학교',
  '동산고등학교',
  '중앙초등학교',
  '푸른중학교',
];

/** 모듈 로드 시점에 1회만 고정한다 — 이후 모든 날짜 계산이 이 값 기준 오프셋이라 같은
 *  프로세스에서는 매 요청 동일한 결과를 반환한다(Math.random 없이 결정적). */
const GENERATED_AT = new Date();

function formatDate(date: Date): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

function subtractDays(base: Date, days: number): Date {
  const result = new Date(base);
  result.setDate(result.getDate() - days);
  return result;
}

function padDigits(value: number, length: number): string {
  const max = 10 ** length;
  const normalized = ((value % max) + max) % max;
  return String(normalized).padStart(length, '0');
}

function buildPhoneNumber(seed: number): string {
  const middle = padDigits(1000 + seed * 137, 4);
  const last = padDigits(2000 + seed * 271, 4);
  return `010-${middle}-${last}`;
}

/**
 * 인덱스 하나로부터 학생 회원 1건을 결정적으로 만든다. 회원코드는 ST00001~ST00128을
 * 생성 순서(index) 그대로 부여하고, 가입일은 "최근 날짜부터 역순"(index가 커질수록 과거)으로
 * 채운다 — 즉 index 0(ST00001)이 가장 최근 가입자, index 127(ST00128)이 가장 오래된
 * 가입자다.
 */
function buildStudentMember(index: number): StudentMember {
  const sequenceNumber = index + 1;
  const memberCode = `${MEMBER_CODE_PREFIX}${padDigits(sequenceNumber, 5)}`;

  // SURNAMES.length(10)와 GIVEN_NAMES.length(13)는 서로소라 둘 다 index를 그대로
  // 모듈러로 써도 (성, 이름) 쌍이 lcm(10,13)=130번째(=128건 범위 밖)에야 반복된다 —
  // floor(index / 10)처럼 나눗셈으로 이름 축을 늦게 회전시키면 성이 한 바퀴(10명) 도는
  // 동안 이름이 고정돼 "김민준·이민준·박민준..." 식으로 화면 첫 페이지가 온통 같은
  // given name으로 보이는 부자연스러운 반복이 생긴다(실제로 확인됨) — 그래서 두 축
  // 모두 index를 직접 쓴다.
  const surname = SURNAMES[index % SURNAMES.length];
  const givenName = GIVEN_NAMES[index % GIVEN_NAMES.length];
  const name = `${surname}${givenName}`;

  const loginId = `stu${padDigits(sequenceNumber, 4)}`;
  const grade = (index % 6) + 1;
  const classNumber = (index % 10) + 1;
  const studentNumber = (index % 30) + 1;

  const guardianGivenName =
    GUARDIAN_GIVEN_NAMES[index % GUARDIAN_GIVEN_NAMES.length];

  const joinedAt = formatDate(subtractDays(GENERATED_AT, index));
  const birthYear = GENERATED_AT.getFullYear() - (grade + 6);
  const birthDate = `${birthYear}-${padDigits((index % 12) + 1, 2)}-${padDigits(
    (index % 28) + 1,
    2
  )}`;

  return {
    id: memberCode,
    memberCode,
    name,
    loginId,
    phoneNumber: buildPhoneNumber(index),
    email: `${loginId}@example.com`,
    role: '학생',
    schoolName: SCHOOL_NAMES[index % SCHOOL_NAMES.length],
    grade,
    classNumber,
    studentNumber,
    guardianName: `${surname}${guardianGivenName}`,
    guardianPhoneNumber: buildPhoneNumber(index + 500),
    joinedAt,
    birthDate,
    // 11명 중 1명 꼴로 비활성 — 조회 팝업의 사용여부 라디오가 실제로 두 상태 모두를
    // 반영하는지 검증할 수 있도록 결정적으로 소수를 비활성화한다.
    isActive: index % 11 !== 0,
  };
}

let mockStudentMembers: StudentMember[] = Array.from(
  { length: TOTAL_MOCK_COUNT },
  (_, index) => buildStudentMember(index)
);

function matchesKeyword(
  member: StudentMember,
  field: StudentMemberSearchField,
  keyword: string
): boolean {
  return member[field].toLowerCase().includes(keyword);
}

/**
 * 검색·정렬·페이징이 적용된 학생 회원 목록을 조회한다.
 * 캐시 전략: `no-store` 상당 — searchParams 기반이라 조건이 매 요청 달라지고 개인정보를
 * 포함하므로 재사용 캐시를 두지 않는다.
 */
export async function fetchStudentMembers(
  query: StudentMemberQuery
): Promise<{ items: StudentMember[]; totalCount: number }> {
  const keyword = query.keyword.trim().toLowerCase();

  const filtered = keyword
    ? mockStudentMembers.filter((member) =>
        matchesKeyword(member, query.searchField, keyword)
      )
    : mockStudentMembers;

  const sorted = [...filtered].sort((a, b) =>
    query.sort === 'name'
      ? a.name.localeCompare(b.name, 'ko')
      : b.joinedAt.localeCompare(a.joinedAt)
  );

  const totalCount = sorted.length;
  const start = (query.page - 1) * query.pageSize;
  const items = sorted.slice(start, start + query.pageSize);

  return { items, totalCount };
}

/**
 * 단건 조회 — Server Action의 입력 검증(존재 여부 확인)에 사용한다.
 * 캐시 전략: `no-store` 상당(위와 동일한 이유).
 */
export async function fetchStudentMemberById(
  id: string
): Promise<StudentMember | null> {
  return mockStudentMembers.find((member) => member.id === id) ?? null;
}

/**
 * 사용여부만 갱신한다(조회 팝업에서 유일하게 수정 가능한 필드). mock 한정 — 모듈 레벨 배열을
 * 불변 갱신(map으로 새 배열 생성)하고 프로세스 생존 동안 유지한다. 쓰기 함수라 캐시 대상이
 * 아니다 — 호출부(Server Action)가 `revalidatePath('/students')`로 목록 화면을 재검증한다.
 */
export async function updateStudentMemberActiveStatus(
  id: string,
  isActive: boolean
): Promise<void> {
  const exists = mockStudentMembers.some((member) => member.id === id);
  if (!exists) {
    throw new Error(`존재하지 않는 학생 회원입니다: ${id}`);
  }

  mockStudentMembers = mockStudentMembers.map((member) =>
    member.id === id ? { ...member, isActive } : member
  );
}
