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

export type ChatPermissionRole = 'guest' | 'token-user';
export type ChatTypeExecutionMode = 'default' | 'summary-free-talking' | 'dispatcher-workers';
export type ChatTypeReviewPolicy = 'self' | 'reviewer';
export type ChatTypeResourceReportPolicy = 'none' | 'if-generated' | 'always';
export type ChatTypeParticipantBinding =
  | 'manual'
  | 'first-moderator-rest-conversation'
  | 'first-moderator-rest-conversation-last-reviewer';

export type ChatTypeExecutionPolicy = {
  mode: ChatTypeExecutionMode;
  participantBinding: ChatTypeParticipantBinding;
  reviewPolicy: ChatTypeReviewPolicy;
  resourceReportPolicy: ChatTypeResourceReportPolicy;
  allowModeratorIntervention: boolean;
  finalSummaryRequired: boolean;
};

export type ChatTypeRecord = {
  id: string;
  name: string;
  sortOrder: number;
  description: string;
  executionPolicy: ChatTypeExecutionPolicy;
  permissions: ChatPermissionRole[];
  enabled: boolean;
  updatedAt: string;
};

export type ChatTypeRegistrySnapshot = {
  chatTypes: ChatTypeRecord[];
};

export type ChatTypeInput = {
  id?: string;
  name: string;
  sortOrder?: number;
  description?: string;
  executionPolicy?: Partial<ChatTypeExecutionPolicy>;
  permissions: ChatPermissionRole[];
  enabled?: boolean;
};

const CHAT_TYPES_API_PATH = '/chat-types';
const CHAT_TYPE_SYNC_EVENT = 'work-app:chat-types-changed';
const CHAT_TYPE_REQUEST_TIMEOUT_MS = 8000;
export const CHAT_PERMISSION_ROLE_LABELS: Record<ChatPermissionRole, string> = {
  guest: '게스트',
  'token-user': '토큰 사용자',
};

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

function buildChatTypeNameKey(value: string | null | undefined) {
  return normalizeText(value).replace(/\s+/g, ' ').toLocaleLowerCase('ko-KR');
}

function normalizePermissions(permissions: ChatPermissionRole[] | null | undefined): ChatPermissionRole[] {
  const nextPermissions = Array.from(
    new Set(
      (permissions ?? []).filter(
        (permission): permission is ChatPermissionRole => permission === 'guest' || permission === 'token-user',
      ),
    ),
  );
  return nextPermissions.length > 0 ? nextPermissions : (['token-user'] as ChatPermissionRole[]);
}

export function createDefaultChatTypeExecutionPolicy(
  mode: ChatTypeExecutionMode = 'default',
): ChatTypeExecutionPolicy {
  if (mode === 'summary-free-talking') {
    return {
      mode,
      participantBinding: 'first-moderator-rest-conversation',
      reviewPolicy: 'self',
      resourceReportPolicy: 'if-generated',
      allowModeratorIntervention: false,
      finalSummaryRequired: true,
    };
  }

  if (mode === 'dispatcher-workers') {
    return {
      mode,
      participantBinding: 'first-moderator-rest-conversation',
      reviewPolicy: 'self',
      resourceReportPolicy: 'if-generated',
      allowModeratorIntervention: true,
      finalSummaryRequired: true,
    };
  }

  return {
    mode,
    participantBinding: 'manual',
    reviewPolicy: 'self',
    resourceReportPolicy: 'if-generated',
    allowModeratorIntervention: true,
    finalSummaryRequired: false,
  };
}

function normalizeExecutionMode(value: string | null | undefined): ChatTypeExecutionMode {
  if (value === 'summary-free-talking' || value === 'dispatcher-workers') {
    return value;
  }

  return 'default';
}

function normalizeExecutionPolicy(
  value: Partial<ChatTypeExecutionPolicy> | ChatTypeExecutionPolicy | null | undefined,
): ChatTypeExecutionPolicy {
  const mode = normalizeExecutionMode(value?.mode);
  const defaults = createDefaultChatTypeExecutionPolicy(mode);
  const participantBinding =
    value?.participantBinding === 'first-moderator-rest-conversation' ||
    value?.participantBinding === 'first-moderator-rest-conversation-last-reviewer' ||
    value?.participantBinding === 'manual'
      ? value.participantBinding
      : defaults.participantBinding;
  const reviewPolicy = value?.reviewPolicy === 'reviewer' ? 'reviewer' : defaults.reviewPolicy;
  const resourceReportPolicy =
    value?.resourceReportPolicy === 'none' || value?.resourceReportPolicy === 'always'
      ? value.resourceReportPolicy
      : defaults.resourceReportPolicy;

  return {
    mode,
    participantBinding,
    reviewPolicy,
    resourceReportPolicy,
    allowModeratorIntervention:
      typeof value?.allowModeratorIntervention === 'boolean'
        ? value.allowModeratorIntervention
        : defaults.allowModeratorIntervention,
    finalSummaryRequired:
      typeof value?.finalSummaryRequired === 'boolean' ? value.finalSummaryRequired : defaults.finalSummaryRequired,
  };
}

function normalizeSortOrder(value: number | null | undefined) {
  if (typeof value !== 'number' || !Number.isFinite(value)) {
    return null;
  }

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

type NormalizedChatTypeCandidate = Omit<ChatTypeRecord, 'sortOrder'> & {
  sortOrder: number | null;
};

function normalizeChatType(record: Partial<ChatTypeRecord>): NormalizedChatTypeCandidate | null {
  const name = normalizeText(record.name);

  if (!name) {
    return null;
  }

  const id =
    normalizeText(record.id) ||
    `chat-type-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;

  return {
    id,
    name,
    sortOrder: normalizeSortOrder(record.sortOrder),
    description: normalizeText(record.description),
    executionPolicy: normalizeExecutionPolicy(record.executionPolicy),
    permissions: normalizePermissions(record.permissions),
    enabled: record.enabled !== false,
    updatedAt: typeof record.updatedAt === 'string' && record.updatedAt ? record.updatedAt : new Date().toISOString(),
  };
}

function getChatTypeSemanticKey(record: Pick<ChatTypeRecord, 'name'>) {
  return buildChatTypeNameKey(record.name);
}

function compareUpdatedAt(left: Pick<ChatTypeRecord, 'updatedAt'>, right: Pick<ChatTypeRecord, 'updatedAt'>) {
  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 dedupeChatTypes(chatTypes: NormalizedChatTypeCandidate[]) {
  const bySemanticKey = new Map<string, NormalizedChatTypeCandidate>();

  for (const item of chatTypes) {
    const semanticKey = getChatTypeSemanticKey(item);
    const current = bySemanticKey.get(semanticKey);

    if (!current) {
      bySemanticKey.set(semanticKey, item);
      continue;
    }

    bySemanticKey.set(semanticKey, compareUpdatedAt(current, item) <= 0 ? item : current);
  }

  return Array.from(bySemanticKey.values())
    .sort((left, right) => {
      const leftSortOrder = normalizeSortOrder(left.sortOrder);
      const rightSortOrder = normalizeSortOrder(right.sortOrder);

      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 nameCompare = left.name.localeCompare(right.name, 'ko-KR');

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

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

function sanitizeChatTypes(chatTypes: Partial<ChatTypeRecord>[]) {
  return dedupeChatTypes(
    chatTypes
      .map((item) => normalizeChatType(item))
      .filter((item): item is NormalizedChatTypeCandidate => Boolean(item)),
  );
}

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

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

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

  return '/api';
}

function resolveChatTypesFallbackBaseUrl() {
  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_TYPES_API_BASE_URL = resolveChatTypesApiBaseUrl();
const CHAT_TYPES_FALLBACK_BASE_URL = resolveChatTypesFallbackBaseUrl();

async function requestChatTypesOnce<T>(baseUrl: string, init?: RequestInit) {
  const headers = appendClientIdHeader(init?.headers);
  const hasBody = init?.body !== undefined && init.body !== null;
  const controller = new AbortController();
  const timeoutId = window.setTimeout(() => controller.abort(), CHAT_TYPE_REQUEST_TIMEOUT_MS);

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

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

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

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

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

    if (!shouldRetryWithFallback) {
      throw error;
    }

    return requestChatTypesOnce<T>(CHAT_TYPES_FALLBACK_BASE_URL, init);
  }
}

async function fetchChatTypesFromServer() {
  const response = await requestChatTypes<{
    ok: boolean;
    chatTypes?: Partial<ChatTypeRecord>[] | null;
  }>({
    method: 'GET',
  });

  return {
    chatTypes: sanitizeChatTypes(response.chatTypes ?? []),
  } satisfies ChatTypeRegistrySnapshot;
}

async function saveChatTypesToServer(chatTypes: ChatTypeRecord[]) {
  const response = await requestChatTypes<{
    ok: boolean;
    chatTypes?: Partial<ChatTypeRecord>[] | null;
  }>({
    method: 'PUT',
    body: JSON.stringify({ chatTypes: sanitizeChatTypes(chatTypes) }),
  });

  emitChatTypesChange();
  return {
    chatTypes: sanitizeChatTypes(response.chatTypes ?? []),
  } satisfies ChatTypeRegistrySnapshot;
}

export function upsertChatType(chatTypes: ChatTypeRecord[], input: ChatTypeInput) {
  const nextRecord = normalizeChatType({
    id: input.id,
    name: input.name,
    sortOrder: input.sortOrder,
    description: input.description,
    executionPolicy: normalizeExecutionPolicy(input.executionPolicy),
    permissions: input.permissions,
    enabled: input.enabled,
    updatedAt: new Date().toISOString(),
  });

  if (!nextRecord) {
    return sanitizeChatTypes(chatTypes);
  }

  const nextSemanticKey = getChatTypeSemanticKey(nextRecord);
  const nextChatTypes: Partial<ChatTypeRecord>[] = chatTypes.filter(
    (item) => item.id !== nextRecord.id && getChatTypeSemanticKey(item) !== nextSemanticKey,
  );
  nextChatTypes.push({
    ...nextRecord,
    sortOrder: nextRecord.sortOrder ?? chatTypes.length + 1,
  });
  return sanitizeChatTypes(nextChatTypes);
}

export function deleteChatType(chatTypes: ChatTypeRecord[], chatTypeId: string) {
  const normalizedId = normalizeText(chatTypeId);

  if (!normalizedId) {
    return sanitizeChatTypes(chatTypes);
  }

  return sanitizeChatTypes(chatTypes.filter((item) => item.id !== normalizedId));
}

export function resolveCurrentChatPermissionRoles(hasTokenAccess: boolean): ChatPermissionRole[] {
  return hasTokenAccess ? ['token-user'] : ['guest'];
}

export function canUseChatType(chatType: ChatTypeRecord, roles: ChatPermissionRole[]) {
  return chatType.enabled && chatType.permissions.some((permission) => roles.includes(permission));
}

export function useChatTypeRegistry() {
  const [chatTypes, setChatTypesState] = useState<ChatTypeRecord[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [errorMessage, setErrorMessage] = useState('');
  const isMountedRef = useRef(true);
  const syncChatTypesRef = useRef<() => Promise<ChatTypeRegistrySnapshot>>(async () => ({
    chatTypes: [],
  }));

  const applySnapshot = (snapshot: ChatTypeRegistrySnapshot) => {
    if (!isMountedRef.current) {
      return;
    }

    setChatTypesState(snapshot.chatTypes);
    setErrorMessage('');
  };

  useEffect(() => {
    isMountedRef.current = true;

    const syncChatTypes = async () => {
      setIsLoading(true);
      setErrorMessage('');
      let resolvedChatTypeSnapshot: ChatTypeRegistrySnapshot = {
        chatTypes: [],
      };

      try {
        const serverChatTypeSnapshot = await fetchChatTypesFromServer();
        resolvedChatTypeSnapshot = serverChatTypeSnapshot ?? resolvedChatTypeSnapshot;
        applySnapshot(resolvedChatTypeSnapshot);
      } catch (error) {
        if (isMountedRef.current) {
          setChatTypesState([]);
          setErrorMessage(error instanceof Error ? error.message : '채팅유형을 불러오지 못했습니다.');
        }
        resolvedChatTypeSnapshot = {
          chatTypes: [],
        };
      } finally {
        if (isMountedRef.current) {
          setIsLoading(false);
        }
      }

      return resolvedChatTypeSnapshot;
    };

    syncChatTypesRef.current = syncChatTypes;

    void syncChatTypes();

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

    window.addEventListener(CHAT_TYPE_SYNC_EVENT, handleSync);

    return () => {
      isMountedRef.current = false;
      window.removeEventListener(CHAT_TYPE_SYNC_EVENT, handleSync);
    };
  }, []);

  return {
    chatTypes,
    isLoading,
    errorMessage,
    reload: async () => syncChatTypesRef.current(),
    setChatTypesLocal: (nextChatTypes: ChatTypeRecord[]) => {
      applySnapshot({
        chatTypes: sanitizeChatTypes(nextChatTypes),
      });
    },
    setChatTypes: async (nextChatTypes: ChatTypeRecord[]) => {
      await saveChatTypesToServer(nextChatTypes);
      const resolved = await fetchChatTypesFromServer();
      applySnapshot(resolved);
      return resolved;
    },
  };
}
