import { useEffect, useRef, useState } from 'react';
import { appendClientIdHeader } from './clientIdentity';
import { getRegisteredAccessToken, isAllowedRegistrationToken } from './tokenAccess';

const UNBOUNDED_NUMERIC_LIMIT = Number.MAX_SAFE_INTEGER;

export type TokenSettingRecord = {
  id: string;
  name: string;
  description: string;
  defaultExpiresInMinutes: number;
  maxExpiresInMinutes: number;
  maxTokensPer30Days: number;
  maxTokensPer7Days: number;
  maxTokensPer5Hours: number;
  oneTimeTokenLimit: number;
  allowedAppIds: string[];
  enabled: boolean;
  updatedAt: string;
};

export type TokenSettingActivityRecord = {
  id: number;
  settingId: string;
  activityType: 'created' | 'updated' | 'deleted';
  actorLabel: string | null;
  summary: string;
  detail: string | null;
  clientIp: string | null;
  externalIp: string | null;
  forwardedFor: string | null;
  realIp: string | null;
  host: string | null;
  origin: string | null;
  referer: string | null;
  userAgent: string | null;
  clientId: string | null;
  createdAt: string;
};

export type TokenSettingInput = {
  originalId?: string;
  id?: string;
  name: string;
  description?: string;
  defaultExpiresInMinutes?: number;
  maxExpiresInMinutes?: number;
  maxTokensPer30Days?: number;
  maxTokensPer7Days?: number;
  maxTokensPer5Hours?: number;
  oneTimeTokenLimit?: number;
  allowedAppIds?: string[];
  enabled?: boolean;
};

const TOKEN_SETTINGS_API_PATH = '/token-settings';
const TOKEN_SETTINGS_SYNC_EVENT = 'work-app:token-settings-changed';
const TOKEN_SETTINGS_REQUEST_TIMEOUT_MS = 8000;

type TokenSettingsRequestOptions = {
  shareToken?: string | null;
  path?: string | null;
};

class TokenSettingApiError extends Error {
  status: number;

  constructor(message: string, status: number) {
    super(message);
    this.name = 'TokenSettingApiError';
    this.status = status;
  }
}

function normalizeText(value: string | null | undefined) {
  return value?.trim() ?? '';
}

function normalizeSettingId(value: string | null | undefined) {
  return normalizeText(value)
    .toLowerCase()
    .replace(/\s+/g, '-')
    .replace(/[^a-z0-9._-]/g, '');
}

function normalizePositiveInteger(value: number | undefined, fallback: number, min: number, max: number) {
  if (value === undefined || !Number.isFinite(value)) {
    return fallback;
  }

  return Math.min(max, Math.max(min, Math.round(value)));
}

function normalizeAllowedAppIds(value: string[] | null | undefined) {
  return Array.from(
    new Set(
      (value ?? [])
        .map((item) => normalizeText(item))
        .filter(Boolean),
    ),
  ).sort((left, right) => left.localeCompare(right, 'en'));
}

function normalizeTokenSetting(record: Partial<TokenSettingRecord>): TokenSettingRecord | null {
  const id = normalizeSettingId(record.id);
  const name = normalizeText(record.name);

  if (!id || !name) {
    return null;
  }

  const defaultExpiresInMinutes = normalizePositiveInteger(record.defaultExpiresInMinutes, 60, 0, UNBOUNDED_NUMERIC_LIMIT);
  const resolvedMaxExpiresInMinutes = normalizePositiveInteger(
    record.maxExpiresInMinutes,
    defaultExpiresInMinutes <= 0 ? 0 : 10_080,
    0,
    UNBOUNDED_NUMERIC_LIMIT,
  );
  const maxExpiresInMinutes =
    defaultExpiresInMinutes <= 0 || resolvedMaxExpiresInMinutes <= 0
      ? 0
      : Math.max(defaultExpiresInMinutes, resolvedMaxExpiresInMinutes);

  const legacyMaxTotalTokens =
    'maxTotalTokens' in record
      ? normalizePositiveInteger((record as { maxTotalTokens?: number }).maxTotalTokens, 100_000, 0, UNBOUNDED_NUMERIC_LIMIT)
      : 100_000;

  return {
    id,
    name,
    description: normalizeText(record.description),
    defaultExpiresInMinutes,
    maxExpiresInMinutes,
    maxTokensPer30Days: normalizePositiveInteger(record.maxTokensPer30Days, legacyMaxTotalTokens, 0, UNBOUNDED_NUMERIC_LIMIT),
    maxTokensPer7Days: normalizePositiveInteger(record.maxTokensPer7Days, legacyMaxTotalTokens, 0, UNBOUNDED_NUMERIC_LIMIT),
    maxTokensPer5Hours: normalizePositiveInteger(record.maxTokensPer5Hours, legacyMaxTotalTokens, 0, UNBOUNDED_NUMERIC_LIMIT),
    oneTimeTokenLimit: normalizePositiveInteger(record.oneTimeTokenLimit, 0, 0, UNBOUNDED_NUMERIC_LIMIT),
    allowedAppIds: normalizeAllowedAppIds(record.allowedAppIds),
    enabled: record.enabled !== false,
    updatedAt: normalizeText(record.updatedAt) || new Date().toISOString(),
  };
}

export function sanitizeTokenSettings(items: Partial<TokenSettingRecord>[] | null | undefined) {
  const byId = new Map<string, TokenSettingRecord>();

  for (const item of items ?? []) {
    const normalized = normalizeTokenSetting(item);
    if (!normalized) {
      continue;
    }

    const current = byId.get(normalized.id);
    if (!current || Date.parse(current.updatedAt) <= Date.parse(normalized.updatedAt)) {
      byId.set(normalized.id, normalized);
    }
  }

  return Array.from(byId.values()).sort((left, right) => {
    const nameCompare = left.name.localeCompare(right.name, 'ko-KR');
    if (nameCompare !== 0) {
      return nameCompare;
    }

    return left.id.localeCompare(right.id, 'en');
  });
}

function emitTokenSettingsChange() {
  if (typeof window === 'undefined') {
    return;
  }

  window.dispatchEvent(new Event(TOKEN_SETTINGS_SYNC_EVENT));
}

function resolveApiBaseUrl() {
  if (import.meta.env.VITE_WORK_SERVER_URL) {
    return import.meta.env.VITE_WORK_SERVER_URL;
  }

  return '/api';
}

function resolveFallbackBaseUrl() {
  if (typeof window === 'undefined') {
    return null;
  }

  const hostname = window.location.hostname;
  if (!['localhost', '127.0.0.1', '0.0.0.0'].includes(hostname)) {
    return null;
  }

  const fallbackUrl = new URL(window.location.origin);
  fallbackUrl.port = '3100';
  fallbackUrl.pathname = '/api';
  fallbackUrl.search = '';
  fallbackUrl.hash = '';
  return fallbackUrl.toString().replace(/\/+$/, '');
}

const API_BASE_URL = resolveApiBaseUrl();
const FALLBACK_BASE_URL = resolveFallbackBaseUrl();

async function requestOnce<T>(baseUrl: string, init?: RequestInit, options?: TokenSettingsRequestOptions): Promise<T> {
  const headers = appendClientIdHeader(init?.headers);
  const token = getRegisteredAccessToken();
  const hasBody = init?.body !== undefined && init?.body !== null;
  const controller = new AbortController();
  const timeoutId = window.setTimeout(() => controller.abort(), TOKEN_SETTINGS_REQUEST_TIMEOUT_MS);
  const shareToken = options?.shareToken?.trim() ?? '';

  if (!shareToken && !isAllowedRegistrationToken(token)) {
    throw new TokenSettingApiError('권한 토큰 등록 후에만 토큰 설정을 관리할 수 있습니다.', 403);
  }

  if (hasBody && !headers.has('Content-Type')) {
    headers.set('Content-Type', 'application/json');
  }

  if (token && !headers.has('X-Access-Token')) {
    headers.set('X-Access-Token', token);
  }

  if (shareToken && !headers.has('X-Chat-Share-Token')) {
    headers.set('X-Chat-Share-Token', shareToken);
  }

  try {
    const response = await fetch(`${baseUrl}${options?.path?.trim() || TOKEN_SETTINGS_API_PATH}`, {
      ...init,
      headers,
      signal: controller.signal,
      cache: init?.cache ?? (init?.method?.toUpperCase() === 'GET' ? 'no-store' : undefined),
    });

    if (!response.ok) {
      const text = await response.text();

      try {
        const payload = JSON.parse(text) as { message?: string };
        throw new TokenSettingApiError(payload.message || '토큰 설정 요청에 실패했습니다.', response.status);
      } catch {
        throw new TokenSettingApiError(text || '토큰 설정 요청에 실패했습니다.', response.status);
      }
    }

    const text = await response.text();

    if (!text.trim()) {
      throw new TokenSettingApiError('토큰 설정 응답이 비어 있습니다.', 502);
    }

    try {
      return JSON.parse(text) as T;
    } catch {
      throw new TokenSettingApiError('토큰 설정 응답을 해석하지 못했습니다.', 502);
    }
  } finally {
    window.clearTimeout(timeoutId);
  }
}

async function requestTokenSettings<T>(init?: RequestInit, options?: TokenSettingsRequestOptions) {
  try {
    return await requestOnce<T>(API_BASE_URL, init, options);
  } catch (error) {
    const shouldRetryWithFallback =
      FALLBACK_BASE_URL &&
      FALLBACK_BASE_URL !== API_BASE_URL &&
      (error instanceof TokenSettingApiError
        ? error.status === 404 || error.status === 408 || error.status === 502
        : error instanceof Error && /404|408|502|Failed to fetch|Load failed|NetworkError|응답이 지연/i.test(error.message));

    if (!shouldRetryWithFallback) {
      throw error;
    }

    return requestOnce<T>(FALLBACK_BASE_URL, init, options);
  }
}

function normalizeActivityRecord(record: Partial<TokenSettingActivityRecord>): TokenSettingActivityRecord {
  return {
    id: Number(record.id ?? 0),
    settingId: normalizeSettingId(record.settingId),
    activityType: record.activityType === 'created' || record.activityType === 'deleted' ? record.activityType : 'updated',
    actorLabel: normalizeText(record.actorLabel) || null,
    summary: normalizeText(record.summary),
    detail: normalizeText(record.detail) || null,
    clientIp: normalizeText(record.clientIp) || null,
    externalIp: normalizeText(record.externalIp) || null,
    forwardedFor: normalizeText(record.forwardedFor) || null,
    realIp: normalizeText(record.realIp) || null,
    host: normalizeText(record.host) || null,
    origin: normalizeText(record.origin) || null,
    referer: normalizeText(record.referer) || null,
    userAgent: normalizeText(record.userAgent) || null,
    clientId: normalizeText(record.clientId) || null,
    createdAt: normalizeText(record.createdAt) || new Date().toISOString(),
  };
}

async function loadTokenSettingsFromServer(options?: TokenSettingsRequestOptions) {
  const response = await requestTokenSettings<{ ok: boolean; tokenSettings: Partial<TokenSettingRecord>[] | null }>({
    method: 'GET',
  }, options);

  return sanitizeTokenSettings(response.tokenSettings);
}

async function saveTokenSettingsToServer(items: TokenSettingRecord[], options?: TokenSettingsRequestOptions) {
  const resolved = sanitizeTokenSettings(items);
  const response = await requestTokenSettings<{ ok: boolean; tokenSettings: Partial<TokenSettingRecord>[] }>({
    method: 'PUT',
    body: JSON.stringify({ tokenSettings: resolved }),
  }, options);

  return sanitizeTokenSettings(response.tokenSettings);
}

export async function fetchTokenSettingActivities(settingId: string, options?: TokenSettingsRequestOptions) {
  const normalizedSettingId = normalizeSettingId(settingId);
  if (!normalizedSettingId) {
    return [];
  }

  const response = await requestTokenSettings<{ ok: boolean; activities: Partial<TokenSettingActivityRecord>[] | null }>(
    { method: 'GET' },
    { shareToken: options?.shareToken, path: `${TOKEN_SETTINGS_API_PATH}/${encodeURIComponent(normalizedSettingId)}/activities` },
  );

  return Array.isArray(response.activities) ? response.activities.map((item) => normalizeActivityRecord(item)) : [];
}

export function upsertTokenSetting(items: TokenSettingRecord[], input: TokenSettingInput) {
  const nextItem = normalizeTokenSetting({
    ...input,
    id: input.id,
  });

  if (!nextItem) {
    return sanitizeTokenSettings(items);
  }

  const originalId = normalizeSettingId(input.originalId);
  const nextItems = items.filter((item) => item.id !== nextItem.id && item.id !== originalId);
  nextItems.push(nextItem);
  return sanitizeTokenSettings(nextItems);
}

export function deleteTokenSetting(items: TokenSettingRecord[], tokenSettingId: string) {
  return sanitizeTokenSettings(items.filter((item) => item.id !== tokenSettingId));
}

export function useTokenSettingRegistry(enabled = true, options?: TokenSettingsRequestOptions) {
  const [tokenSettings, setTokenSettingsState] = useState<TokenSettingRecord[]>([]);
  const [isLoading, setIsLoading] = useState(enabled);
  const [errorMessage, setErrorMessage] = useState('');
  const loadVersionRef = useRef(0);
  const shareToken = options?.shareToken?.trim() ?? '';

  useEffect(() => {
    if (!enabled) {
      setTokenSettingsState([]);
      setIsLoading(false);
      setErrorMessage('');
      return undefined;
    }

    let mounted = true;

    const load = async () => {
      const loadVersion = ++loadVersionRef.current;
      setIsLoading(true);
      setErrorMessage('');

      try {
        const nextItems = await loadTokenSettingsFromServer({ shareToken });
        if (!mounted || loadVersion !== loadVersionRef.current) {
          return;
        }

        setTokenSettingsState(nextItems);
      } catch (error) {
        if (!mounted || loadVersion !== loadVersionRef.current) {
          return;
        }

        setErrorMessage(error instanceof Error ? error.message : '토큰 설정을 불러오지 못했습니다.');
      } finally {
        if (mounted && loadVersion === loadVersionRef.current) {
          setIsLoading(false);
        }
      }
    };

    void load();

    const handleSync = () => {
      void load();
    };

    window.addEventListener(TOKEN_SETTINGS_SYNC_EVENT, handleSync);
    return () => {
      mounted = false;
      window.removeEventListener(TOKEN_SETTINGS_SYNC_EVENT, handleSync);
    };
  }, [enabled, shareToken]);

  const setTokenSettings = async (items: TokenSettingRecord[]) => {
    const savedItems = await saveTokenSettingsToServer(items, { shareToken });
    setTokenSettingsState(savedItems);
    emitTokenSettingsChange();
    return savedItems;
  };

  return {
    tokenSettings,
    setTokenSettings,
    isLoading,
    errorMessage,
  };
}
