'use client';

import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { TokenData, ResultDto, ApiError } from '@/types/response/common';
import { CLAIM_NAME_IDENTIFIER, CLAIM_EMAIL, CLAIM_NAME } from '@/constants/common';

// 서버·클라이언트 공용 순수 유틸 재export (기존 import 호환)
export { formatDate, getDateTime, isDateOverdue } from './format';

export function cn(...inputs: ClassValue[]) {
	return twMerge(clsx(inputs))
}

// 클라이언트용 fetch 유틸 (Route Handler 호출용)
interface FetchApiOptions extends Omit<RequestInit, 'body'> {
	body?: unknown;
	/** true로 설정 시 에러 응답을 throw하지 않고 ResultDto를 그대로 반환 */
	silent?: boolean;
}

// 토큰 갱신 동기화 — 동일 탭 내 중복 + 다중 탭 동기화 + 네트워크 재시도
let refreshPromise: Promise<boolean>|null = null;

// 다중 탭 동기화 — 한 탭이 refresh 중일 때 다른 탭은 결과를 기다림
// 회전+재사용 감지 정책 하에서 같은 refreshToken으로 동시 호출 시 전체 세션 폐기되는 race 방지
type RefreshBroadcastMsg =
	| { type: 'refresh-start'; nonce: string }
	| { type: 'refresh-done'; nonce: string; success: boolean };

const refreshChannel: BroadcastChannel|null = typeof window !== 'undefined' && typeof BroadcastChannel !== 'undefined'
	? new BroadcastChannel('antooza-auth-refresh')
	: null;

let externalRefreshWaiter: { promise: Promise<boolean>; resolve: (v: boolean) => void; nonce: string }|null = null;

if (refreshChannel) {
	refreshChannel.addEventListener('message', (e: MessageEvent<RefreshBroadcastMsg>) => {
		const msg = e.data;
		if (msg.type === 'refresh-start') {
			// 다른 탭이 refresh 시작 — 우리도 동시 refresh 하지 않고 대기
			if (!externalRefreshWaiter && !refreshPromise) {
				let resolveFn!: (v: boolean) => void;
				const p = new Promise<boolean>(r => { resolveFn = r; });
				externalRefreshWaiter = { promise: p, resolve: resolveFn, nonce: msg.nonce };
				console.log('[Auth] Refresh in progress in another tab — waiting', msg.nonce);
				// 안전장치: 10초 안에 done 안 오면 자체 refresh
				setTimeout(() => {
					if (externalRefreshWaiter?.nonce === msg.nonce) {
						console.warn('[Auth] External refresh timeout — falling back to self refresh');
						externalRefreshWaiter.resolve(false);
						externalRefreshWaiter = null;
					}
				}, 10000);
			}
		} else if (msg.type === 'refresh-done') {
			if (externalRefreshWaiter?.nonce === msg.nonce) {
				console.log('[Auth] External refresh done', msg.nonce, 'success=', msg.success);
				externalRefreshWaiter.resolve(msg.success);
				externalRefreshWaiter = null;
			}
		}
	});
}

async function tryRefreshToken(): Promise<boolean> {
	// 1) 같은 탭 내 동시 호출 — 같은 promise 공유
	if (refreshPromise) {
		return refreshPromise;
	}

	// 2) 다른 탭이 이미 refresh 중 — 결과 기다림
	if (externalRefreshWaiter) {
		return externalRefreshWaiter.promise;
	}

	const nonce = Math.random().toString(36).slice(2);
	refreshChannel?.postMessage({ type: 'refresh-start', nonce } satisfies RefreshBroadcastMsg);

	refreshPromise = (async () => {
		// 네트워크 일시 끊김 대비 재시도 (최대 3회). 단, 명시적 인증 실패(401/403)는 즉시 종료.
		for (let attempt = 0; attempt < 3; attempt++) {
			try {
				const httpRes = await fetch('/api/auth/refresh-token', { method: 'POST' });
				const data: ResultDto<unknown> = await httpRes.json();

				if (data.success) {
					console.log('[Auth] Refresh success', `attempt=${attempt + 1}`);
					return true;
				}

				if (data.status === 401 || data.status === 403) {
					console.warn('[Auth] Refresh rejected (auth)', `status=${data.status}`, data.message);
					return false;
				}
				console.warn('[Auth] Refresh non-auth failure — retrying', `status=${data.status}`, `attempt=${attempt + 1}`);
			} catch (err) {
				console.warn('[Auth] Refresh network error — retrying', `attempt=${attempt + 1}`, err);
			}
			await new Promise(r => setTimeout(r, 300 * (attempt + 1)));
		}
		console.error('[Auth] Refresh failed after 3 attempts');
		return false;
	})();

	// 결과 broadcast + promise 해제
	const currentPromise = refreshPromise;
	currentPromise.then(success => {
		refreshChannel?.postMessage({ type: 'refresh-done', nonce, success } satisfies RefreshBroadcastMsg);
	}).finally(() => {
		if (refreshPromise === currentPromise) {
			refreshPromise = null;
		}
	});

	return refreshPromise;
}

export async function fetchApi<T>(url: string, options?: FetchApiOptions): Promise<ResultDto<T>>
{
	const isFormData = options?.body instanceof FormData;
	const hasBody = options?.body !== undefined;

	const fetchOptions: RequestInit = {
		...options,
		headers: {
			...(!isFormData && hasBody ? { 'Content-Type': 'application/json' } : {}),
			...options?.headers
		},
		body: isFormData ? options.body as BodyInit : (options?.body ? JSON.stringify(options.body) : undefined)
	};

	let res: ResultDto<T> = await (await fetch(url, fetchOptions)).json();

	// 401 시 토큰 갱신 후 재시도
	if (!res.success && res.status === 401) {
		const refreshed = await tryRefreshToken();
		if (refreshed) {
			res = await (await fetch(url, fetchOptions)).json();
		} else if (typeof window !== 'undefined') {
			// silent 모드여도 인증 실패 시 강제 로그아웃 이벤트 발행 (UI 와 실제 상태 어긋남 방지)
			window.dispatchEvent(new CustomEvent('auth:unauthorized'));
		}
	}

	// 기본: 에러 시 throw (silent 옵션으로 비활성화)
	if (!options?.silent) {
		throwError(res);
	}

	return res;
}

// JWT 토큰에서 사용자 정보 추출
export function decodeAccessToken(token: string): TokenData|null
{
	try {

		const base64URL = token.split('.')[1]; // JWT의 Payload 부분
        const base64 = base64URL.replace(/-/g, '+').replace(/_/g, '/'); // Base64 형식 변환
        const jsonPayload = decodeURIComponent(
			atob(base64).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')
        );

        const payload = JSON.parse(jsonPayload);

		return {
			id: payload[CLAIM_NAME_IDENTIFIER] || null,
			email: payload[CLAIM_EMAIL] || null,
			name: payload[CLAIM_NAME] || null
		};

	} catch {
 		return null;
	}
}

// 파일 이름 생성
export function getDateFilename(prefix: string): string {
	if (!prefix) {
		return '';
	}

    const now = new Date();
    const yyyy = now.getFullYear();
    const MM = String(now.getMonth() + 1).padStart(2, '0');
    const dd = String(now.getDate()).padStart(2, '0');
    const hh = String(now.getHours()).padStart(2, '0');
    const mm = String(now.getMinutes()).padStart(2, '0');
    const ss = String(now.getSeconds()).padStart(2, '0');

    return `${prefix}-${yyyy}${MM}${dd}${hh}${mm}${ss}.png`;
}

// HTML 태그 제거
export function stripHtmlTags(str?: string|null): string {
	if (!str) {
		return '';
	}

	const div = document.createElement('div');
	div.innerHTML = str;
	return div.textContent || div.innerText || '';
}

// 비밀번호 정책 안내 문구 생성성
export function getPasswordPolicyMessage(policy: {
	passwordUppercaseLength: number;
	passwordNumbersLength: number;
	passwordSpecialcharsLength: number;
}) {
	const parts: string[] = [];

	if (policy.passwordUppercaseLength > 0) {
		parts.push(`영문 대문자 ${policy.passwordUppercaseLength}자 이상`);
	}

	if (policy.passwordNumbersLength > 0) {
		parts.push(`숫자 ${policy.passwordNumbersLength}자 이상`);
	}

	if (policy.passwordSpecialcharsLength > 0) {
		parts.push(`특수문자 ${policy.passwordSpecialcharsLength}자 이상`);
	}

	if (parts.length > 0) {
		return `비밀번호는 ${parts.join(', ')}를 포함해야 합니다.`;
	}

	return '';
}

// 특수문자 제거
export function filterSpecialCharacters(str: string) {
	return str.replace(/[^\p{L}\p{N}\s]/gu, '');
}

// ResultDto에서 첫 번째 오류 조회
function getError(errors: ApiError[]|null|undefined, index = 0): string|undefined {
    if (!errors || errors.length === 0) {
		return undefined;
	}
    return errors[index]?.description;
}

// 서버 응답 메시지 분석
function throwError<T>(res: ResultDto<T>): void {
	if (res.success) {
		return;
	}

	let message: string|null|undefined = getError(res.errors);

	if (!message) {
		message = res.message;
	}

	if (!message) {
		switch (res.status) {
			case 400: message = '잘못된 요청입니다.'; break;
			case 401:
				message = '로그인 후 이용해 주세요.';

				// 인증 만료 이벤트 발행
				window.dispatchEvent(new CustomEvent('auth:unauthorized'));;
				break;
			case 403: message = '권한이 없습니다.'; break;
			case 404: message = '요청하신 페이지를 찾을 수 없습니다.'; break;
			case 500: message = '서버 오류가 발생했습니다. 다시 시도해 주세요.'; break;
		}
	}

	if (message) {
		throw new Error(message);
	}
}

// 1000회 이상 조회된 게시글인지 확인
export function isHotPost(showHotIcon: boolean, post: { views: number; createdAt: string }): boolean {
    return (showHotIcon && post.views >= 1000 && Date.now() - new Date(post.createdAt).getTime() < 7 * 24 * 60 * 60 * 1000);
}

// 24시간 이내에 작성된 게시글인지 확인
export function isNewPost(showNewIcon: boolean, post: { createdAt: string }): boolean {
    return (showNewIcon && Date.now() - new Date(post.createdAt).getTime() < 24 * 60 * 60 * 1000);
}
