import 'server-only';
import { backendFetch } from '@/lib/http/backend-fetch';
import { decodeJwtPayload } from '@/lib/auth/decode-jwt-payload';
import type { AdminUser } from '@/lib/domain/admin-user';

/**
 * 관리자 인증 Repository — 백엔드(edupay-backend) 실연동.
 *
 * 엔드포인트: `POST /api/v1/common/auth/admin/login` (baseURL: lib/env.ts의 getApiBaseUrl()).
 * 요청 필드명은 `loginPw`다(`password`가 아님 — 백엔드 계약 그대로).
 *
 * 백엔드에는 관리자 프로필 조회 API가 없다(2026-08 기준) — 그래서 로그인 응답으로 받은
 * accessToken(JWT)의 payload를 디코딩해 AdminUser를 구성한다. 서명 검증은 하지 않는다
 * (백엔드 시크릿이 없어 불가능 — lib/auth/decode-jwt-payload.ts 참고, 위조 방지는 이후
 * 우리 세션 HMAC 서명이 담당한다). 관리자 "이름" 클레임이 토큰에 없어 loginId로 대체한다
 * (사용자 확정 사항).
 */

export type AdminCredentials = {
  loginId: string;
  password: string;
};

export type AdminLoginResult = {
  admin: AdminUser;
  accessToken: string;
  refreshToken: string;
  /** 백엔드 accessToken의 `exp` 클레임(unix seconds) — 세션 만료를 이 값에 정렬시키는 데 쓴다. */
  accessTokenExpiresAt: number;
};

const ADMIN_LOGOUT_PATH = '/api/v1/common/auth/logout';

/** 백엔드 accessToken(JWT)의 payload 클레임. */
type AdminAccessTokenClaims = {
  sub: string;
  loginId: string;
  admRoleCd: string;
  adminId: string;
  userType: string;
  userId: string;
  iat: number;
  exp: number;
};

type AdminLoginResponse = {
  accessToken: string;
  refreshToken: string;
};

/**
 * 관리자 로그인을 시도한다. 자격 불일치·존재하지 않는 계정 등 인증 실패는 예외가 아니라
 * 정상 흐름이므로 null을 반환한다(네트워크·파싱 등 진짜 예외 상황은 lib/http/backend-fetch.ts가
 * 흡수해 동일하게 `ok:false`로 내려주므로 이 함수 입장에서는 실패 사유를 구분하지 않는다 —
 * 호출부가 항상 일반화된 오류 메시지로 응답하기 때문에 구분할 필요가 없다).
 *
 * 캐시 전략: `no-store` — 인증 요청은 재사용 캐시 대상이 아니다(매 시도가 백엔드에 도달해야 함).
 */
export async function requestAdminLogin(
  credentials: AdminCredentials
): Promise<AdminLoginResult | null> {
  const result = await backendFetch<AdminLoginResponse>(
    '/api/v1/common/auth/admin/login',
    {
      method: 'POST',
      body: { loginId: credentials.loginId, loginPw: credentials.password },
      cache: 'no-store',
    }
  );

  if (!result.ok) {
    return null;
  }

  const claims = decodeJwtPayload<AdminAccessTokenClaims>(result.data.accessToken);
  if (
    !claims ||
    typeof claims.adminId !== 'string' ||
    !claims.adminId ||
    typeof claims.loginId !== 'string' ||
    !claims.loginId ||
    typeof claims.admRoleCd !== 'string' ||
    !claims.admRoleCd ||
    typeof claims.exp !== 'number'
  ) {
    return null;
  }

  const admin: AdminUser = {
    id: claims.adminId,
    // 백엔드 accessToken에 관리자 이름 클레임이 없어 loginId로 대체한다(사용자 확정 사항).
    name: claims.loginId,
    loginId: claims.loginId,
    roleCode: claims.admRoleCd,
  };

  return {
    admin,
    accessToken: result.data.accessToken,
    refreshToken: result.data.refreshToken,
    accessTokenExpiresAt: claims.exp,
  };
}

/**
 * 관리자 로그아웃을 백엔드에 알린다 — `GET /api/v1/common/auth/logout`(인증 필요, AUTH_WHITELIST에
 * 없음을 확인함: edupay-backend SecurityConfig.java).
 *
 * JWT는 stateless라 이 호출이 accessToken 자체를 무효화하지는 않는다 — 발급된 토큰은 만료까지
 * 계속 유효하다. 백엔드가 실질적으로 하는 일은 FCM 토큰 삭제뿐이다(edupay-backend
 * CmmAuthApiController#actionLogoutJSON: `deleteFcmToken` + `SecurityContextLogoutHandler.logout`,
 * 후자는 STATELESS 세션 정책상 사실상 무의미). 그래도 정식 절차이므로 호출은 한다.
 *
 * 성공 응답의 data는 항상 null이다(`ApiResponseVO.success(null)`) — `canHaveNullData: true`로
 * 이 정상 응답을 실패로 오판하지 않게 한다(lib/http/backend-fetch.ts 참고).
 *
 * 실패(백엔드 다운·accessToken 만료 등)를 예외로 다루지 않고 boolean으로 반환한다 — 로그아웃은
 * 백엔드 호출이 실패해도 반드시 로컬에서는 진행돼야 하므로, 호출부(Server Action)가 try/catch
 * 없이 결과만 보고 흐름을 결정할 수 있게 한다.
 *
 * 캐시 전략: `no-store` — 상태를 변경하는 요청이라 캐시 대상이 아니다.
 */
export async function requestAdminLogout(accessToken: string): Promise<boolean> {
  const result = await backendFetch<null>(ADMIN_LOGOUT_PATH, {
    method: 'GET',
    accessToken,
    cache: 'no-store',
    canHaveNullData: true,
  });

  return result.ok;
}
