import { useEffect, useSyncExternalStore } from 'react';
import { appendClientIdHeader } from './clientIdentity';

export type ChatDefaultContextRecord = {
  id: string;
  title: string;
  sortOrder: number;
  content: string;
  enabled: boolean;
  updatedAt: string;
};

export type ChatTypeDefaultContextSelection = {
  chatTypeId: string;
  defaultContextIds: string[];
  updatedAt: string;
};

export type ChatRoomCodexParticipant = {
  id: string;
  name: string;
  model: string;
  prompt: string;
  chatTypeId: string | null;
  defaultContextIds: string[];
  role: 'default' | 'moderator' | 'conversation' | 'reviewer';
};

export type ChatRoomContextSettings = {
  sessionId: string;
  defaultContextIds: string[];
  customContextTitle: string;
  customContextContent: string;
  codexParticipants: ChatRoomCodexParticipant[];
  updatedAt: string;
};

type ChatContextSettingsStore = {
  defaultContexts: ChatDefaultContextRecord[];
  chatTypeDefaults: ChatTypeDefaultContextSelection[];
  roomContexts: ChatRoomContextSettings[];
};

export type ChatContextSettingsStoreSource = 'server' | 'optimistic';

const CHAT_CONTEXT_SETTINGS_SYNC_EVENT = 'work-app:chat-context-settings-changed';
const CHAT_CONTEXT_SETTINGS_API_PATH = '/chat-context-settings';
const CHAT_CONTEXT_SETTINGS_REQUEST_TIMEOUT_MS = 8000;

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

function compareUpdatedAt(left: { updatedAt: string }, right: { updatedAt: string }) {
  const leftTime = Date.parse(left.updatedAt);
  const rightTime = Date.parse(right.updatedAt);

  if (Number.isFinite(leftTime) && Number.isFinite(rightTime) && leftTime !== rightTime) {
    return leftTime - rightTime;
  }

  return 0;
}

function normalizePositiveSortOrder(value: unknown) {
  if (typeof value !== 'number' || !Number.isFinite(value)) {
    return Number.NaN;
  }

  const nextValue = Math.trunc(value);
  return nextValue > 0 ? nextValue : Number.NaN;
}

function normalizeDefaultContext(record: Partial<ChatDefaultContextRecord>): ChatDefaultContextRecord | null {
  const title = normalizeText(record.title);
  const content = normalizeText(record.content);

  if (!title && !content) {
    return null;
  }

  return {
    id:
      normalizeText(record.id) ||
      `chat-default-context-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
    title: title || '기본 유형',
    sortOrder: normalizePositiveSortOrder(record.sortOrder),
    content,
    enabled: record.enabled !== false,
    updatedAt: normalizeText(record.updatedAt) || new Date().toISOString(),
  };
}

function normalizeDefaultContextIds(defaultContextIds: string[] | null | undefined) {
  return Array.from(new Set((defaultContextIds ?? []).map((item) => normalizeText(item)).filter(Boolean)));
}

function sanitizeCodexParticipants(items: Partial<ChatRoomCodexParticipant>[] | null | undefined) {
  return Array.from(
    new Map(
      (items ?? [])
        .map((item, index) => {
          const name = normalizeText(item.name);
          const model = normalizeText(item.model);
          const prompt = normalizeText(item.prompt);
          const chatTypeId = normalizeText(item.chatTypeId) || null;
          const defaultContextIds = normalizeDefaultContextIds(item.defaultContextIds);
          const role =
            normalizeText(item.role) === 'moderator'
              ? 'moderator'
              : normalizeText(item.role) === 'conversation'
                ? 'conversation'
                : normalizeText(item.role) === 'reviewer'
                  ? 'reviewer'
                : 'default';
          const id = normalizeText(item.id) || `codex-participant-${index + 1}`;

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

          return [
            id,
              {
                id,
                name,
                model,
                prompt,
                chatTypeId,
                defaultContextIds,
                role,
              } satisfies ChatRoomCodexParticipant,
          ] as const;
        })
        .filter((item): item is readonly [string, ChatRoomCodexParticipant] => Boolean(item)),
    ).values(),
  );
}

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

  (items ?? [])
    .map((item) => normalizeDefaultContext(item))
    .filter((item): item is ChatDefaultContextRecord => Boolean(item))
    .forEach((item) => {
      const current = byId.get(item.id);

      if (!current || compareUpdatedAt(current, item) <= 0) {
        byId.set(item.id, item);
      }
    });

  return Array.from(byId.values())
    .sort((left, right) => {
      const leftSortOrder = Number.isFinite(left.sortOrder) ? left.sortOrder : null;
      const rightSortOrder = Number.isFinite(right.sortOrder) ? right.sortOrder : null;

      if (leftSortOrder !== null && rightSortOrder !== null && leftSortOrder !== rightSortOrder) {
        return leftSortOrder - rightSortOrder;
      }

      if (leftSortOrder !== null && rightSortOrder === null) {
        return -1;
      }

      if (leftSortOrder === null && rightSortOrder !== null) {
        return 1;
      }

      const titleCompare = left.title.localeCompare(right.title, 'ko-KR');

      if (titleCompare !== 0) {
        return titleCompare;
      }

      return compareUpdatedAt(left, right);
    })
    .map((item, index) => ({
      ...item,
      sortOrder: index + 1,
    }));
}

function sanitizeChatTypeDefaultSelections(items: Partial<ChatTypeDefaultContextSelection>[] | null | undefined) {
  const byChatTypeId = new Map<string, ChatTypeDefaultContextSelection>();

  (items ?? []).forEach((item) => {
    const chatTypeId = normalizeText(item.chatTypeId);

    if (!chatTypeId) {
      return;
    }

    const nextRecord: ChatTypeDefaultContextSelection = {
      chatTypeId,
      defaultContextIds: normalizeDefaultContextIds(item.defaultContextIds),
      updatedAt: normalizeText(item.updatedAt) || new Date().toISOString(),
    };
    const current = byChatTypeId.get(chatTypeId);

    if (!current || compareUpdatedAt(current, nextRecord) <= 0) {
      byChatTypeId.set(chatTypeId, nextRecord);
    }
  });

  return Array.from(byChatTypeId.values()).sort((left, right) => left.chatTypeId.localeCompare(right.chatTypeId, 'ko-KR'));
}

function sanitizeRoomContexts(items: Partial<ChatRoomContextSettings>[] | null | undefined) {
  const bySessionId = new Map<string, ChatRoomContextSettings>();

  (items ?? []).forEach((item) => {
    const sessionId = normalizeText(item.sessionId);

    if (!sessionId) {
      return;
    }

    const nextRecord: ChatRoomContextSettings = {
      sessionId,
      defaultContextIds: normalizeDefaultContextIds(item.defaultContextIds),
      customContextTitle: normalizeText(item.customContextTitle),
      customContextContent: normalizeText(item.customContextContent),
      codexParticipants: sanitizeCodexParticipants(item.codexParticipants),
      updatedAt: normalizeText(item.updatedAt) || new Date().toISOString(),
    };

    const hasCustomContext = Boolean(nextRecord.customContextTitle || nextRecord.customContextContent);
    const hasDefaultOverrides = nextRecord.defaultContextIds.length > 0;
    const hasCodexParticipants = nextRecord.codexParticipants.length > 0;

    if (!hasCustomContext && !hasDefaultOverrides && !hasCodexParticipants) {
      return;
    }

    const current = bySessionId.get(sessionId);

    if (!current || compareUpdatedAt(current, nextRecord) <= 0) {
      bySessionId.set(sessionId, nextRecord);
    }
  });

  return Array.from(bySessionId.values()).sort((left, right) => left.sessionId.localeCompare(right.sessionId, 'ko-KR'));
}

function sanitizeStore(input: Partial<ChatContextSettingsStore> | null | undefined): ChatContextSettingsStore {
  return {
    defaultContexts: sanitizeDefaultContexts(input?.defaultContexts),
    chatTypeDefaults: sanitizeChatTypeDefaultSelections(input?.chatTypeDefaults),
    roomContexts: sanitizeRoomContexts(input?.roomContexts),
  };
}

const EMPTY_CHAT_CONTEXT_SETTINGS_STORE = sanitizeStore(null);

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

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

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

  return '/api';
}

function resolveChatContextSettingsFallbackBaseUrl() {
  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 CHAT_CONTEXT_SETTINGS_API_BASE_URL = resolveChatContextSettingsApiBaseUrl();
const CHAT_CONTEXT_SETTINGS_FALLBACK_BASE_URL = resolveChatContextSettingsFallbackBaseUrl();

async function requestChatContextSettingsOnce<T>(baseUrl: string, init?: RequestInit) {
  const headers = appendClientIdHeader(init?.headers);
  const hasBody = init?.body !== undefined && init.body !== null;
  const normalizedMethod = init?.method?.toUpperCase() ?? 'GET';
  const controller = new AbortController();
  const timeoutId = window.setTimeout(() => controller.abort(), CHAT_CONTEXT_SETTINGS_REQUEST_TIMEOUT_MS);

  // 기본 유형 설정은 origin별 분기 없이 전역 설정을 source of truth로 사용한다.
  headers.delete('X-App-Origin');
  headers.delete('X-App-Domain');

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

  if (normalizedMethod === 'GET') {
    headers.set('Cache-Control', 'no-cache, no-store, max-age=0');
    headers.set('Pragma', 'no-cache');
  }

  try {
    const requestUrl =
      normalizedMethod === 'GET'
        ? `${baseUrl}${CHAT_CONTEXT_SETTINGS_API_PATH}?__ts=${Date.now()}`
        : `${baseUrl}${CHAT_CONTEXT_SETTINGS_API_PATH}`;
    const response = await fetch(requestUrl, {
      ...init,
      headers,
      signal: controller.signal,
      cache: init?.cache ?? (normalizedMethod === 'GET' ? 'reload' : undefined),
    });

    if (!response.ok) {
      throw new Error((await response.text()) || '채팅 Context 설정 요청에 실패했습니다.');
    }

    return response.json() as Promise<T>;
  } finally {
    window.clearTimeout(timeoutId);
  }
}

async function requestChatContextSettings<T>(init?: RequestInit) {
  try {
    return await requestChatContextSettingsOnce<T>(CHAT_CONTEXT_SETTINGS_API_BASE_URL, init);
  } catch (error) {
    const shouldRetryWithFallback =
      CHAT_CONTEXT_SETTINGS_FALLBACK_BASE_URL &&
      CHAT_CONTEXT_SETTINGS_FALLBACK_BASE_URL !== CHAT_CONTEXT_SETTINGS_API_BASE_URL &&
      error instanceof Error &&
      /404|408|502|Failed to fetch|Load failed|NetworkError|응답이 지연/i.test(error.message);

    if (!shouldRetryWithFallback) {
      throw error;
    }

    return requestChatContextSettingsOnce<T>(CHAT_CONTEXT_SETTINGS_FALLBACK_BASE_URL, init);
  }
}

async function fetchStoreFromServer() {
  const response = await requestChatContextSettings<{
    ok: boolean;
    settings?: Partial<ChatContextSettingsStore> | null;
  }>({
    method: 'GET',
  });

  return sanitizeStore(response.settings);
}

async function saveStoreToServer(store: ChatContextSettingsStore) {
  const sanitized = sanitizeStore(store);
  const response = await requestChatContextSettings<{
    ok: boolean;
    settings?: Partial<ChatContextSettingsStore> | null;
  }>({
    method: 'PUT',
    body: JSON.stringify({ settings: sanitized }),
  });

  emitStoreChange();
  return sanitizeStore(response.settings ?? sanitized);
}

type ChatContextSettingsRegistrySnapshot = ChatContextSettingsStore & {
  isLoading: boolean;
  errorMessage: string;
  lastLoadedAt: string | null;
  lastFailedAt: string | null;
  hasLoadedFromServer: boolean;
  storeSource: ChatContextSettingsStoreSource;
};

const EMPTY_CHAT_CONTEXT_SETTINGS_REGISTRY_SNAPSHOT: ChatContextSettingsRegistrySnapshot = {
  ...EMPTY_CHAT_CONTEXT_SETTINGS_STORE,
  isLoading: false,
  errorMessage: '',
  lastLoadedAt: null,
  lastFailedAt: null,
  hasLoadedFromServer: false,
  storeSource: 'server',
};

let chatContextSettingsRegistrySnapshot = EMPTY_CHAT_CONTEXT_SETTINGS_REGISTRY_SNAPSHOT;
let chatContextSettingsRegistryRequestSequence = 0;
let chatContextSettingsRegistryWindowEventsBound = false;
const chatContextSettingsRegistryListeners = new Set<() => void>();

function emitChatContextSettingsRegistryChange() {
  chatContextSettingsRegistryListeners.forEach((listener) => {
    listener();
  });
}

function getChatContextSettingsRegistrySnapshot() {
  return chatContextSettingsRegistrySnapshot;
}

function setChatContextSettingsRegistrySnapshot(
  updater:
    | ChatContextSettingsRegistrySnapshot
    | ((current: ChatContextSettingsRegistrySnapshot) => ChatContextSettingsRegistrySnapshot),
) {
  const nextSnapshot = typeof updater === 'function' ? updater(chatContextSettingsRegistrySnapshot) : updater;

  chatContextSettingsRegistrySnapshot = nextSnapshot;
  emitChatContextSettingsRegistryChange();
}

function subscribeChatContextSettingsRegistry(listener: () => void) {
  chatContextSettingsRegistryListeners.add(listener);

  return () => {
    chatContextSettingsRegistryListeners.delete(listener);
  };
}

async function reloadChatContextSettingsRegistryFromServer() {
  const requestSequence = chatContextSettingsRegistryRequestSequence + 1;
  chatContextSettingsRegistryRequestSequence = requestSequence;

  setChatContextSettingsRegistrySnapshot((current) => ({
    ...current,
    isLoading: true,
  }));

  try {
    const serverStore = await fetchStoreFromServer();
    const saved = sanitizeStore(serverStore);

    if (chatContextSettingsRegistryRequestSequence === requestSequence) {
      setChatContextSettingsRegistrySnapshot((current) => ({
        ...current,
        ...saved,
        isLoading: false,
        errorMessage: '',
        lastLoadedAt: new Date().toISOString(),
        lastFailedAt: null,
        hasLoadedFromServer: true,
        storeSource: 'server',
      }));
    }

    return saved;
  } catch (error) {
    if (chatContextSettingsRegistryRequestSequence === requestSequence) {
      setChatContextSettingsRegistrySnapshot((current) => ({
        ...(current.lastLoadedAt ? current : EMPTY_CHAT_CONTEXT_SETTINGS_REGISTRY_SNAPSHOT),
        isLoading: false,
        errorMessage: error instanceof Error ? error.message : '채팅 Context 설정을 불러오지 못했습니다.',
        lastFailedAt: new Date().toISOString(),
        hasLoadedFromServer: current.lastLoadedAt !== null,
        storeSource: current.lastLoadedAt ? current.storeSource : 'server',
      }));
    }

    throw error;
  }
}

function ensureChatContextSettingsRegistryWindowEvents() {
  if (chatContextSettingsRegistryWindowEventsBound || typeof window === 'undefined' || typeof document === 'undefined') {
    return;
  }

  const handleSync = () => {
    void reloadChatContextSettingsRegistryFromServer().catch(() => undefined);
  };
  const handleWindowFocus = () => {
    if (document.visibilityState === 'hidden') {
      return;
    }

    void reloadChatContextSettingsRegistryFromServer().catch(() => undefined);
  };
  const handleVisibilityChange = () => {
    if (document.visibilityState !== 'visible') {
      return;
    }

    void reloadChatContextSettingsRegistryFromServer().catch(() => undefined);
  };

  window.addEventListener(CHAT_CONTEXT_SETTINGS_SYNC_EVENT, handleSync);
  window.addEventListener('focus', handleWindowFocus);
  window.addEventListener('pageshow', handleWindowFocus);
  document.addEventListener('visibilitychange', handleVisibilityChange);
  chatContextSettingsRegistryWindowEventsBound = true;
}

export function resolveChatTypeDefaultContextIds(
  selections: ChatTypeDefaultContextSelection[],
  chatTypeId: string | null | undefined,
) {
  const normalizedChatTypeId = normalizeText(chatTypeId);

  if (!normalizedChatTypeId) {
    return [];
  }

  return (
    selections.find((item) => item.chatTypeId === normalizedChatTypeId)?.defaultContextIds.map((item) => item.trim()).filter(Boolean) ??
    []
  );
}

export function resolveChatRoomContextSettings(
  roomContexts: ChatRoomContextSettings[],
  sessionId: string | null | undefined,
) {
  const normalizedSessionId = normalizeText(sessionId);

  if (!normalizedSessionId) {
    return null;
  }

  return roomContexts.find((item) => item.sessionId === normalizedSessionId) ?? null;
}

export function upsertChatDefaultContext(
  defaultContexts: ChatDefaultContextRecord[],
  input: Partial<ChatDefaultContextRecord>,
) {
  const currentRecord = defaultContexts.find((item) => item.id === normalizeText(input.id)) ?? null;
  const nextSortOrder =
    normalizePositiveSortOrder(input.sortOrder) ||
    currentRecord?.sortOrder ||
    Math.max(0, ...defaultContexts.map((item) => item.sortOrder || 0)) + 1;
  const nextRecord = normalizeDefaultContext({
    ...currentRecord,
    ...input,
    sortOrder: nextSortOrder,
    updatedAt: new Date().toISOString(),
  });

  if (!nextRecord) {
    return sanitizeDefaultContexts(defaultContexts);
  }

  return sanitizeDefaultContexts([
    ...defaultContexts.filter((item) => item.id !== nextRecord.id),
    nextRecord,
  ]);
}

export function deleteChatDefaultContext(defaultContexts: ChatDefaultContextRecord[], contextId: string) {
  const normalizedContextId = normalizeText(contextId);

  if (!normalizedContextId) {
    return sanitizeDefaultContexts(defaultContexts);
  }

  return sanitizeDefaultContexts(defaultContexts.filter((item) => item.id !== normalizedContextId));
}

export function upsertChatTypeDefaultContextSelection(
  selections: ChatTypeDefaultContextSelection[],
  chatTypeId: string,
  defaultContextIds: string[],
) {
  const normalizedChatTypeId = normalizeText(chatTypeId);

  if (!normalizedChatTypeId) {
    return sanitizeChatTypeDefaultSelections(selections);
  }

  const nextRecord: ChatTypeDefaultContextSelection = {
    chatTypeId: normalizedChatTypeId,
    defaultContextIds: normalizeDefaultContextIds(defaultContextIds),
    updatedAt: new Date().toISOString(),
  };

  return sanitizeChatTypeDefaultSelections([
    ...selections.filter((item) => item.chatTypeId !== normalizedChatTypeId),
    nextRecord,
  ]);
}

export function pruneChatTypeDefaultSelections(
  selections: ChatTypeDefaultContextSelection[],
  contextId: string,
) {
  const normalizedContextId = normalizeText(contextId);

  if (!normalizedContextId) {
    return sanitizeChatTypeDefaultSelections(selections);
  }

  return sanitizeChatTypeDefaultSelections(
    selections.map((item) => ({
      ...item,
      defaultContextIds: item.defaultContextIds.filter((candidate) => candidate !== normalizedContextId),
    })),
  );
}

export function upsertChatRoomContextSettings(
  roomContexts: ChatRoomContextSettings[],
  input: Partial<ChatRoomContextSettings> & { sessionId: string },
) {
  const nextRecord: ChatRoomContextSettings = {
    sessionId: normalizeText(input.sessionId),
    defaultContextIds: normalizeDefaultContextIds(input.defaultContextIds),
    customContextTitle: normalizeText(input.customContextTitle),
    customContextContent: normalizeText(input.customContextContent),
    codexParticipants: sanitizeCodexParticipants(input.codexParticipants),
    updatedAt: new Date().toISOString(),
  };

  if (!nextRecord.sessionId) {
    return sanitizeRoomContexts(roomContexts);
  }

  return sanitizeRoomContexts([
    ...roomContexts.filter((item) => item.sessionId !== nextRecord.sessionId),
    nextRecord,
  ]);
}

export function pruneChatRoomContextSettings(roomContexts: ChatRoomContextSettings[], contextId: string) {
  const normalizedContextId = normalizeText(contextId);

  if (!normalizedContextId) {
    return sanitizeRoomContexts(roomContexts);
  }

  return sanitizeRoomContexts(
    roomContexts.map((item) => ({
      ...item,
      defaultContextIds: item.defaultContextIds.filter((candidate) => candidate !== normalizedContextId),
    })),
  );
}

export function useChatContextSettingsRegistry() {
  const snapshot = useSyncExternalStore(
    subscribeChatContextSettingsRegistry,
    getChatContextSettingsRegistrySnapshot,
    getChatContextSettingsRegistrySnapshot,
  );

  useEffect(() => {
    ensureChatContextSettingsRegistryWindowEvents();
    void reloadChatContextSettingsRegistryFromServer().catch(() => undefined);
  }, []);

  return {
    ...snapshot,
    reload: reloadChatContextSettingsRegistryFromServer,
    setStore: (
      updater:
        | ChatContextSettingsStore
        | ((current: ChatContextSettingsStore) => ChatContextSettingsStore),
    ) => {
      const currentStore: ChatContextSettingsStore = {
        defaultContexts: snapshot.defaultContexts,
        chatTypeDefaults: snapshot.chatTypeDefaults,
        roomContexts: snapshot.roomContexts,
      };
      const nextStore = typeof updater === 'function' ? updater(currentStore) : updater;
      const saved = sanitizeStore(nextStore);

      setChatContextSettingsRegistrySnapshot((current) => ({
        ...current,
        ...saved,
        errorMessage: '',
        lastFailedAt: null,
        storeSource: 'optimistic',
      }));

      return saveStoreToServer(saved)
        .then((serverSaved) => {
          setChatContextSettingsRegistrySnapshot((current) => ({
            ...current,
            ...serverSaved,
            errorMessage: '',
            lastLoadedAt: new Date().toISOString(),
            lastFailedAt: null,
            hasLoadedFromServer: true,
            storeSource: 'server',
          }));

          emitStoreChange();
          return serverSaved;
        })
        .catch(async (error) => {
          setChatContextSettingsRegistrySnapshot((current) => ({
            ...current,
            errorMessage: error instanceof Error ? error.message : '채팅 Context 설정 저장에 실패했습니다.',
            lastFailedAt: new Date().toISOString(),
          }));

          try {
            await reloadChatContextSettingsRegistryFromServer();
          } catch {
            // 저장 실패 직후에도 서버 원본 재조회는 best-effort로 유지한다.
          }

          throw error;
        });
    },
  };
}
