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 — `POST /api/v1/mngr/auth/login`, `POST /api/v1/mngr/auth/logout`.
 *
 * 이슈: 로그인은 성공(200)이면서도 토큰 없이 최근 접속 정보만 돌아올 수 있다 — 다른 곳에
 *   살아 있는 세션이 있는데 아직 [확인]을 누르지 않은 경우다. 이때 화면이 중복 로그인 안내를
 *   띄우고 `forceLogin`으로 재요청해야 실제 토큰이 발급된다(백엔드 MngrAuthServiceImpl).
 *
 * 이슈: accessToken 클레임에 `loginId`가 없다(EgovJwtTokenUtil#generateAccessToken은 userId·
 *   userNm·userType·roleId·tokenVersion만 싣는다). 그래서 loginId는 입력값을 그대로 세션에
 *   보관한다 — 백엔드가 그 아이디로 계정을 찾아 인증에 성공한 값이라 신뢰할 수 있다.
 */

const LOGIN_PATH = '/api/v1/mngr/auth/login';
const LOGOUT_PATH = '/api/v1/mngr/auth/logout';

/** 백엔드 ResponseCode.NOT_FOUND — "로그인 계정이 없습니다". */
const ACCOUNT_NOT_FOUND_CODE = 404;

const GENERIC_LOGIN_ERROR = '아이디 또는 비밀번호가 올바르지 않습니다.';

export type AdminCredentials = {
  loginId: string;
  password: string;
  /** 중복 로그인 안내에서 [확인]을 누른 재시도인가. */
  forceLogin?: boolean;
};

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

/** 다른 곳에 살아 있는 세션이 있을 때 화면이 보여줄 최근 접속 정보. */
export type DuplicateLoginNotice = {
  lastLoginAt: string | null;
  lastLoginIp: string | null;
  currentIp: string | null;
};

export type AdminLoginOutcome =
  | AdminLoginSuccess
  | { status: 'duplicate'; notice: DuplicateLoginNotice }
  | { status: 'failure'; message: string };

type AdminAccessTokenClaims = {
  userId: string;
  userNm: string;
  userType: string;
  roleId: string;
  exp: number;
};

type AdminLoginResponse = {
  accessToken?: string | null;
  refreshToken?: string | null;
  lastLgnDt?: string | null;
  lastLgnIpAddr?: string | null;
  currentIpAddr?: string | null;
};

function readNullableString(value: unknown): string | null {
  return typeof value === 'string' && value.length > 0 ? value : null;
}

export async function requestAdminLogin(
  credentials: AdminCredentials
): Promise<AdminLoginOutcome> {
  const result = await backendFetch<AdminLoginResponse>(LOGIN_PATH, {
    method: 'POST',
    body: {
      loginId: credentials.loginId,
      loginPw: credentials.password,
      isForceLogin: credentials.forceLogin === true,
    },
    cache: 'no-store',
  });

  if (!result.ok) {
    // 계정 존재 여부는 드러내지 않는다. 잠김·탈퇴·실패횟수 안내는 백엔드 문구를 그대로 쓴다.
    return {
      status: 'failure',
      message:
        result.code === ACCOUNT_NOT_FOUND_CODE || !result.message
          ? GENERIC_LOGIN_ERROR
          : result.message,
    };
  }

  const accessToken = readNullableString(result.data.accessToken);
  const refreshToken = readNullableString(result.data.refreshToken);

  if (!accessToken || !refreshToken) {
    return {
      status: 'duplicate',
      notice: {
        lastLoginAt: readNullableString(result.data.lastLgnDt),
        lastLoginIp: readNullableString(result.data.lastLgnIpAddr),
        currentIp: readNullableString(result.data.currentIpAddr),
      },
    };
  }

  const claims = decodeJwtPayload<AdminAccessTokenClaims>(accessToken);
  if (
    !claims ||
    typeof claims.userId !== 'string' ||
    !claims.userId ||
    typeof claims.exp !== 'number'
  ) {
    return { status: 'failure', message: GENERIC_LOGIN_ERROR };
  }

  const admin: AdminUser = {
    id: claims.userId,
    name:
      typeof claims.userNm === 'string' && claims.userNm
        ? claims.userNm
        : credentials.loginId,
    loginId: credentials.loginId,
    roleCode: typeof claims.roleId === 'string' ? claims.roleId : '',
  };

  return {
    status: 'success',
    admin,
    accessToken,
    refreshToken,
    accessTokenExpiresAt: claims.exp,
  };
}

/**
 * 로그아웃 — 백엔드가 저장된 refreshToken을 NULL로 만든다.
 *
 * 이슈: 이 호출이 accessToken 자체를 무효화하지는 않는다. JwtAuthenticationFilter의 중복 로그인
 *   판정이 `dbRefreshToken != null && !equals(clientTokenVersion)`이라, NULL이 된 뒤에는 기존
 *   accessToken이 만료(24h)까지 계속 통과한다(edupay-backend 61d8197 확인). 백엔드 보완 필요.
 *
 * 실패해도 로컬 세션은 반드시 파기해야 하므로 예외 대신 boolean을 돌려준다.
 */
export async function requestAdminLogout(accessToken: string): Promise<boolean> {
  const result = await backendFetch<null>(LOGOUT_PATH, {
    method: 'POST',
    accessToken,
    cache: 'no-store',
    canHaveNullData: true,
  });

  return result.ok;
}
