import { Injectable, LoggerService } from "@nestjs/common";
import axios, { AxiosResponse } from "axios";
import { axiosAdapter } from "point3-common-tool";

import {
    OAuthClient,
    SignInType,
} from "./oauth-client";

import {
    LogtoOAuthRESTTemplate,
} from "./types";

// DI token for LogtoLoginSession
export const LogtoLoginSessionToken = Symbol.for("LogtoLoginSession");

@Injectable()
/**
 * LogtoLoginSession
 *
 * Logto 인증 세션 플로우를 관리하는 클래스입니다.
 * NestJS DI 환경에서 사용되며, Logto 로그인 과정의 각 단계를 API 호출로 래핑합니다.
 *
 * 주요 역할:
 *   - 로그인 세션 시작 및 세션 쿠키 관리
 *   - 인증 플로우(로그인 경험) 단계별 진행
 *   - 비밀번호 검증, 사용자 식별, 동의 및 제출 처리
 *   - 커스텀 로그인 플로우를 위한 저수준 제어 제공
 *
 * 사용 예시:
 *   const session = new LogtoLoginSession(...);
 *   await session.createSignInSession(...);
 *   ...
 */
export class LogtoLoginSession {
    /**
     * /api 엔드포인트 요청을 위한 REST 템플릿입니다.
     */
    private readonly apiRestTemplate: axiosAdapter.RESTTemplate;

    /**
     * LogtoLoginSession 생성자
     *
     * @param apiUrl - Logto API URL
     * @param logger - 로깅 서비스
     * @param oauthClient - OAuth 클라이언트 인스턴스
     */
    constructor(
        private readonly apiUrl: string,
        private readonly logger: LoggerService,
        private readonly oauthClient: OAuthClient,
    ) {
        // API 기본 URL로 REST 템플릿 초기화
        this.apiRestTemplate = new LogtoOAuthRESTTemplate(this.logger, apiUrl);
    }

    /**
     * Logto 로그인 세션을 시작합니다.
     *
     * 인증 URI를 요청하여 로그인 플로우를 시작하며, 응답에는 세션 쿠키가 포함됩니다.
     *
     * @param signInType - 로그인 유형(예: Admin, Dashboard 등)
     * @returns Axios 응답 객체와 생성된 state 문자열을 포함하는 객체
     */
    public async createSignInSession(
        signInType: SignInType,
    ): Promise<{ response: AxiosResponse | undefined; state: string }> {
        const { uri, state } = this.oauthClient.getSignInURI(signInType);
        const response = await axios.get(uri, {
            maxRedirects: 0,
            validateStatus: (status) => status >= 200 && status <= 400,
            withCredentials: true,
        });
        return { response, state };
    }

    /**
     * Logto 인증 경험(로그인 플로우 1단계)을 시작합니다.
     *
     * 로그인 UI를 트리거하고 상호작용 이벤트를 설정합니다.
     *
     * @param cookie - 초기 로그인 세션에서 받은 세션 쿠키
     * @returns 경험(Experience) 엔드포인트의 응답 데이터
     * @throws 시작 실패 시 에러 발생
     */
    public async experienceSignIn(cookie: string): Promise<any> {
        try {
            const response = await this.apiRestTemplate.put(
                `/experience`,
                { interactionEvent: 'SignIn' },
                {
                    headers: {
                        'Content-Type': 'application/json',
                        Cookie: cookie,
                    },
                    withCredentials: true,
                },
            );
            return response.data;
        } catch (error) {
            this.logger.error('Failed to start login experience');
            throw error;
        }
    }

    /**
     * 로그인 플로우 중 사용자의 비밀번호를 검증합니다.
     *
     * @param cookie - 세션 쿠키
     * @param dto - identifier(타입/값)와 비밀번호를 포함한 객체
     * @returns 비밀번호 검증 엔드포인트의 응답 데이터
     * @throws 검증 실패 시 에러 발생
     */
    public async verificationPassword(
        cookie: string,
        dto: {
            identifier: {
                type: string;
                value: string;
            };
            password: string;
        },
    ): Promise<any> {
        try {
            const response = await this.apiRestTemplate.post(
                `/experience/verification/password`,
                {
                    identifier: dto.identifier,
                    password: dto.password,
                },
                {
                    headers: { Cookie: cookie, "Accept-Language": 'ko-KR, ko;'  },
                    withCredentials: true,
                },
            );
            return response.data;
        } catch (error) {
            throw error;
        }
    }

    /**
     * 로그인 플로우에서 verificationId를 사용해 사용자를 식별합니다.
     *
     * @param cookie - 세션 쿠키
     * @param verificationId - 이전 단계에서 받은 verification ID
     * @returns 식별 엔드포인트의 응답 데이터
     * @throws 식별 실패 시 에러 발생
     */
    public async identify(cookie: string, verificationId: string): Promise<any> {
        try {
            const response = await this.apiRestTemplate.post(
                `/experience/identification`,
                { verificationId },
                {
                    headers: { Cookie: cookie },
                    withCredentials: true,
                },
            );
            return response.data;
        } catch (error) {
            throw error;
        }
    }

    /**
     * 인증 경험(Experience)을 제출하여 로그인 플로우를 완료합니다.
     *
     * @param cookie - 세션 쿠키
     * @returns 제출 엔드포인트의 응답 데이터
     * @throws 제출 실패 시 에러 발생
     */
    public async submit(cookie: string): Promise<any> {
        try {
            const response = await this.apiRestTemplate.post(
                `/experience/submit`,
                {},
                {
                    headers: { Cookie: cookie },
                    withCredentials: true,
                },
            );
            return response.data;
        } catch (error) {
            throw error;
        }
    }

    /**
     * 사용자를 동의(Consent) 화면으로 리다이렉트합니다.
     *
     * 인증 후 토큰 발급 전 동의 화면으로 이동할 때 사용합니다.
     *
     * @param redirectTo - 동의 페이지로 리다이렉트할 URL
     * @param cookie - 세션 쿠키
     * @returns 리다이렉트 요청의 Axios 응답 객체
     */
    public async redirectToConsent(redirectTo: string, cookie: string): Promise<AxiosResponse> {
        const response = await axios.get(redirectTo, {
            maxRedirects: 0,
            validateStatus: (status) => status >= 200 && status <= 400,
            withCredentials: true,
            headers: { Cookie: cookie },
        });
        return response;
    }

    /**
     * 사용자의 동의(Consent)를 Logto 서버에 제출합니다.
     *
     * @param cookie - 세션 쿠키
     * @returns 동의 엔드포인트의 응답 데이터
     * @throws 동의 제출 실패 시 에러 발생
     */
    public async consent(cookie: string): Promise<any> {
        try {
            const response = await this.apiRestTemplate.post(
                `/interaction/consent`,
                {},
                {
                    headers: { Cookie: cookie },
                    withCredentials: true,
                },
            );
            return response.data;
        } catch (error) {
            throw error;
        }
    }
}
