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

export type SharedResourcePermission = 'view' | 'download' | 'comment' | 'upload' | 'manage';
export type SharedResourceType = 'file' | 'directory' | 'document' | 'chat-share' | 'external-url';

export type SharedResourceLinkedTokenSettingSummary = {
  id: string;
  name: string;
  enabled: boolean;
  defaultExpiresInMinutes: number;
  allowedAppIds: string[];
  syncState: 'ok' | 'missing' | 'disabled' | 'chat-share-disallowed';
  syncMessage: string | null;
};

export type SharedResourceTokenSettingSnapshot = {
  id: string;
  name: string;
  defaultExpiresInMinutes: number;
  maxTokensPer30Days: number;
  maxTokensPer7Days: number;
  maxTokensPer5Hours: number;
  oneTimeTokenLimit: number;
  allowedAppIds: string[];
};

export type SharedResourceTokenRecord = {
  id: string;
  name: string;
  description: string;
  tokenSettingId: string | null;
  tokenSettingSnapshot: SharedResourceTokenSettingSnapshot | null;
  linkedTokenSetting: SharedResourceLinkedTokenSettingSummary | null;
  resourceLabel: string;
  resourcePath: string;
  resourceType: SharedResourceType;
  shareToken: string;
  sharePath: string;
  resourceAllowedAppIds: string[];
  resourceAllowedAppIdsOverrideEnabled: boolean;
  allowedAppIds: string[];
  permissions: SharedResourcePermission[];
  enabled: boolean;
  revokedAt: string | null;
  deletedAt: string | null;
  expiresAt: string | null;
  effectiveExpiresAt: string | null;
  usageLimit: number;
  usageCount: number;
  hasAccessPin: boolean;
  accessPinPromptTtlMinutes: number | null;
  allowAccessPinChangeWithoutManage: boolean;
  usageTokenTotal: number;
  usageRequestCount: number;
  usageCompletedRequestCount: number;
  lastUsedAt: string | null;
  lastTokenUsedAt: string | null;
  createdAt: string;
  updatedAt: string;
  lastActivityAt: string | null;
  activityCount: number;
};

export type SharedResourceTokenActivityRecord = {
  id: number;
  tokenId: string;
  type: 'created' | 'updated' | 'permission-granted' | 'permission-revoked' | 'revoked' | 'restored' | 'deleted' | 'usage';
  actorLabel: string | null;
  summary: string;
  detail: string | null;
  usageDelta: number;
  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 SharedResourceTokenDetail = {
  token: SharedResourceTokenRecord;
  activities: SharedResourceTokenActivityRecord[];
};

export type SharedResourceTokenBulkActionResult = {
  requestedTokenIds: string[];
  processedTokenIds: string[];
  skippedTokenIds: string[];
  missingTokenIds: string[];
};

export type SharedResourceTokenInput = {
  id?: string;
  name: string;
  description?: string;
  tokenSettingId?: string | null;
  resourceLabel: string;
  resourcePath: string;
  resourceType: SharedResourceType;
  shareToken?: string | null;
  sharePath?: string | null;
  resourceAllowedAppIds?: string[] | null;
  resourceAllowedAppIdsOverrideEnabled?: boolean;
  permissions: SharedResourcePermission[];
  enabled: boolean;
  expiresAt?: string | null;
  usageLimit?: number;
  accessPin?: string | null;
  accessPinPromptTtlMinutes?: number | null;
  allowAccessPinChangeWithoutManage?: boolean;
};

type SharedResourceTokenRequestOptions = {
  shareToken?: string | null;
};

const SHARED_RESOURCE_TOKENS_API_PATH = '/shared-resource-tokens';
const SHARED_RESOURCE_TOKENS_SYNC_EVENT = 'work-app:shared-resource-tokens-changed';
const REQUEST_TIMEOUT_MS = 8000;

class SharedResourceTokenApiError extends Error {
  status: number;

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

function isAbortRequestError(error: unknown) {
  return error instanceof DOMException && error.name === 'AbortError';
}

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

  window.dispatchEvent(new Event(SHARED_RESOURCE_TOKENS_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();

function normalizeRequiredText(value: unknown) {
  return typeof value === 'string' ? value.trim() : '';
}

function normalizeOptionalText(value: unknown) {
  const normalized = normalizeRequiredText(value);
  return normalized || null;
}

function normalizeStringArray(value: unknown) {
  if (!Array.isArray(value)) {
    return [];
  }

  return value.map((item) => normalizeRequiredText(item)).filter(Boolean);
}

function normalizeBoolean(value: unknown, fallback = false) {
  return typeof value === 'boolean' ? value : fallback;
}

function normalizeNumber(value: unknown, fallback = 0) {
  const normalized = Number(value);
  return Number.isFinite(normalized) ? normalized : fallback;
}

function normalizeSharedResourceTokenRecord(record: SharedResourceTokenRecord): SharedResourceTokenRecord {
  return {
    ...record,
    id: normalizeRequiredText(record.id),
    name: normalizeRequiredText(record.name),
    description: normalizeRequiredText(record.description),
    tokenSettingId: normalizeOptionalText(record.tokenSettingId),
    tokenSettingSnapshot: record.tokenSettingSnapshot
      ? {
          id: normalizeRequiredText(record.tokenSettingSnapshot.id),
          name: normalizeRequiredText(record.tokenSettingSnapshot.name),
          defaultExpiresInMinutes: normalizeNumber(record.tokenSettingSnapshot.defaultExpiresInMinutes),
          maxTokensPer30Days: normalizeNumber(record.tokenSettingSnapshot.maxTokensPer30Days),
          maxTokensPer7Days: normalizeNumber(record.tokenSettingSnapshot.maxTokensPer7Days),
          maxTokensPer5Hours: normalizeNumber(record.tokenSettingSnapshot.maxTokensPer5Hours),
          oneTimeTokenLimit: normalizeNumber(record.tokenSettingSnapshot.oneTimeTokenLimit),
          allowedAppIds: normalizeStringArray(record.tokenSettingSnapshot.allowedAppIds),
        }
      : null,
    linkedTokenSetting: record.linkedTokenSetting
      ? {
          ...record.linkedTokenSetting,
          id: normalizeRequiredText(record.linkedTokenSetting.id),
          name: normalizeRequiredText(record.linkedTokenSetting.name),
          enabled: normalizeBoolean(record.linkedTokenSetting.enabled),
          defaultExpiresInMinutes: normalizeNumber(record.linkedTokenSetting.defaultExpiresInMinutes),
          allowedAppIds: normalizeStringArray(record.linkedTokenSetting.allowedAppIds),
          syncMessage: normalizeOptionalText(record.linkedTokenSetting.syncMessage),
        }
      : null,
    resourceLabel: normalizeRequiredText(record.resourceLabel),
    resourcePath: normalizeRequiredText(record.resourcePath),
    shareToken: normalizeRequiredText(record.shareToken),
    sharePath: normalizeRequiredText(record.sharePath),
    resourceAllowedAppIds: normalizeStringArray(record.resourceAllowedAppIds),
    resourceAllowedAppIdsOverrideEnabled: normalizeBoolean(record.resourceAllowedAppIdsOverrideEnabled),
    allowedAppIds: normalizeStringArray(record.allowedAppIds),
    permissions: Array.isArray(record.permissions)
      ? record.permissions.filter(
          (item): item is SharedResourcePermission =>
            item === 'view' || item === 'download' || item === 'comment' || item === 'upload' || item === 'manage',
        )
      : [],
    enabled: normalizeBoolean(record.enabled, true),
    revokedAt: normalizeOptionalText(record.revokedAt),
    deletedAt: normalizeOptionalText(record.deletedAt),
    expiresAt: normalizeOptionalText(record.expiresAt),
    effectiveExpiresAt: normalizeOptionalText(record.effectiveExpiresAt),
    usageLimit: normalizeNumber(record.usageLimit),
    usageCount: normalizeNumber(record.usageCount),
    hasAccessPin: normalizeBoolean(record.hasAccessPin),
    accessPinPromptTtlMinutes:
      record.accessPinPromptTtlMinutes === null ? null : normalizeNumber(record.accessPinPromptTtlMinutes),
    allowAccessPinChangeWithoutManage: normalizeBoolean(record.allowAccessPinChangeWithoutManage),
    usageTokenTotal: normalizeNumber(record.usageTokenTotal),
    usageRequestCount: normalizeNumber(record.usageRequestCount),
    usageCompletedRequestCount: normalizeNumber(record.usageCompletedRequestCount),
    lastUsedAt: normalizeOptionalText(record.lastUsedAt),
    lastTokenUsedAt: normalizeOptionalText(record.lastTokenUsedAt),
    createdAt: normalizeRequiredText(record.createdAt),
    updatedAt: normalizeRequiredText(record.updatedAt),
    lastActivityAt: normalizeOptionalText(record.lastActivityAt),
    activityCount: normalizeNumber(record.activityCount),
  };
}

function normalizeSharedResourceTokenActivityRecord(
  record: SharedResourceTokenActivityRecord,
): SharedResourceTokenActivityRecord {
  return {
    ...record,
    id: normalizeNumber(record.id),
    tokenId: normalizeRequiredText(record.tokenId),
    actorLabel: normalizeOptionalText(record.actorLabel),
    summary: normalizeRequiredText(record.summary),
    detail: normalizeOptionalText(record.detail),
    usageDelta: normalizeNumber(record.usageDelta),
    clientIp: normalizeOptionalText(record.clientIp),
    externalIp: normalizeOptionalText(record.externalIp),
    forwardedFor: normalizeOptionalText(record.forwardedFor),
    realIp: normalizeOptionalText(record.realIp),
    host: normalizeOptionalText(record.host),
    origin: normalizeOptionalText(record.origin),
    referer: normalizeOptionalText(record.referer),
    userAgent: normalizeOptionalText(record.userAgent),
    clientId: normalizeOptionalText(record.clientId),
    createdAt: normalizeRequiredText(record.createdAt),
  };
}

async function requestOnce<T>(baseUrl: string, path: string, init?: RequestInit, options?: SharedResourceTokenRequestOptions): 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(), REQUEST_TIMEOUT_MS);
  const shareToken = options?.shareToken?.trim() ?? '';

  if (!shareToken && !isAllowedRegistrationToken(token)) {
    throw new SharedResourceTokenApiError('권한 토큰 등록 후에만 공유 리소스 관리를 사용할 수 있습니다.', 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}${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 SharedResourceTokenApiError(payload.message || '공유 리소스 토큰 요청에 실패했습니다.', response.status);
      } catch {
        throw new SharedResourceTokenApiError(text || '공유 리소스 토큰 요청에 실패했습니다.', response.status);
      }
    }

    const text = await response.text();

    if (!text.trim()) {
      throw new SharedResourceTokenApiError('공유 리소스 토큰 응답이 비어 있습니다.', 502);
    }

    try {
      return JSON.parse(text) as T;
    } catch {
      throw new SharedResourceTokenApiError('공유 리소스 토큰 응답을 해석하지 못했습니다.', 502);
    }
  } catch (error) {
    if (isAbortRequestError(error)) {
      throw error;
    }

    throw error;
  } finally {
    window.clearTimeout(timeoutId);
  }
}

async function request<T>(path: string, init?: RequestInit, options?: SharedResourceTokenRequestOptions) {
  try {
    return await requestOnce<T>(API_BASE_URL, path, init, options);
  } catch (error) {
    const shouldRetryWithFallback =
      FALLBACK_BASE_URL &&
      FALLBACK_BASE_URL !== API_BASE_URL &&
      (error instanceof SharedResourceTokenApiError
        ? 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, path, init, options);
  }
}

export async function loadSharedResourceTokens(options?: SharedResourceTokenRequestOptions) {
  const response = await request<{ ok: boolean; items: SharedResourceTokenRecord[] }>(SHARED_RESOURCE_TOKENS_API_PATH, undefined, options);
  return Array.isArray(response.items) ? response.items.map((item) => normalizeSharedResourceTokenRecord(item)) : [];
}

export async function loadSharedResourceTokenDetail(tokenId: string, options?: SharedResourceTokenRequestOptions) {
  const response = await request<{ ok: boolean; token: SharedResourceTokenRecord; activities: SharedResourceTokenActivityRecord[] }>(
    `${SHARED_RESOURCE_TOKENS_API_PATH}/${encodeURIComponent(tokenId)}`,
    undefined,
    options,
  );

  return {
    ...response,
    token: normalizeSharedResourceTokenRecord(response.token),
    activities: Array.isArray(response.activities)
      ? response.activities.map((item) => normalizeSharedResourceTokenActivityRecord(item))
      : [],
  };
}

export async function saveSharedResourceToken(input: SharedResourceTokenInput, options?: SharedResourceTokenRequestOptions) {
  const payload = {
    ...input,
    title: input.name,
  };
  const response = await request<{ ok: boolean; token: SharedResourceTokenRecord; activities: SharedResourceTokenActivityRecord[] }>(
    SHARED_RESOURCE_TOKENS_API_PATH,
    {
      method: 'PUT',
      body: JSON.stringify(payload),
    },
    options,
  );
  emitSharedResourceTokensChange();
  return {
    ...response,
    token: normalizeSharedResourceTokenRecord(response.token),
    activities: Array.isArray(response.activities)
      ? response.activities.map((item) => normalizeSharedResourceTokenActivityRecord(item))
      : [],
  };
}

export async function revokeSharedResourceToken(tokenId: string, reason = '', options?: SharedResourceTokenRequestOptions) {
  const response = await request<{ ok: boolean; token: SharedResourceTokenRecord; activities: SharedResourceTokenActivityRecord[] }>(
    `${SHARED_RESOURCE_TOKENS_API_PATH}/${encodeURIComponent(tokenId)}/revoke`,
    {
      method: 'POST',
      body: JSON.stringify({ reason }),
    },
    options,
  );
  emitSharedResourceTokensChange();
  return {
    ...response,
    token: normalizeSharedResourceTokenRecord(response.token),
    activities: Array.isArray(response.activities)
      ? response.activities.map((item) => normalizeSharedResourceTokenActivityRecord(item))
      : [],
  };
}

export async function revokeSharedResourceTokens(tokenIds: string[], reason = '', options?: SharedResourceTokenRequestOptions) {
  const response = await request<{ ok: boolean } & SharedResourceTokenBulkActionResult>(
    `${SHARED_RESOURCE_TOKENS_API_PATH}/bulk-revoke`,
    {
      method: 'POST',
      body: JSON.stringify({ tokenIds, reason }),
    },
    options,
  );
  emitSharedResourceTokensChange();
  return response;
}

export async function restoreSharedResourceToken(tokenId: string, options?: SharedResourceTokenRequestOptions) {
  const response = await request<{ ok: boolean; token: SharedResourceTokenRecord; activities: SharedResourceTokenActivityRecord[] }>(
    `${SHARED_RESOURCE_TOKENS_API_PATH}/${encodeURIComponent(tokenId)}/restore`,
    {
      method: 'POST',
    },
    options,
  );
  emitSharedResourceTokensChange();
  return {
    ...response,
    token: normalizeSharedResourceTokenRecord(response.token),
    activities: Array.isArray(response.activities)
      ? response.activities.map((item) => normalizeSharedResourceTokenActivityRecord(item))
      : [],
  };
}

export async function deleteSharedResourceToken(tokenId: string, options?: SharedResourceTokenRequestOptions) {
  const response = await request<{ ok: boolean; deleted: boolean; tokenId: string }>(
    `${SHARED_RESOURCE_TOKENS_API_PATH}/${encodeURIComponent(tokenId)}`,
    {
      method: 'DELETE',
    },
    options,
  );
  emitSharedResourceTokensChange();
  return response;
}

export async function deleteSharedResourceTokens(tokenIds: string[], options?: SharedResourceTokenRequestOptions) {
  const response = await request<{ ok: boolean } & SharedResourceTokenBulkActionResult>(
    `${SHARED_RESOURCE_TOKENS_API_PATH}/bulk-delete`,
    {
      method: 'POST',
      body: JSON.stringify({ tokenIds }),
    },
    options,
  );
  emitSharedResourceTokensChange();
  return response;
}

export function useSharedResourceTokenRegistry(options?: SharedResourceTokenRequestOptions) {
  const [tokens, setTokens] = useState<SharedResourceTokenRecord[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [errorMessage, setErrorMessage] = useState('');
  const refreshRef = useRef<(() => Promise<void>) | null>(null);
  const shareToken = options?.shareToken?.trim() ?? '';

  useEffect(() => {
    let active = true;

    const refresh = async () => {
      setIsLoading(true);
      setErrorMessage('');

      try {
        const nextItems = await loadSharedResourceTokens({ shareToken });

        if (!active) {
          return;
        }

        setTokens(nextItems);
      } catch (error) {
        if (!active || isAbortRequestError(error)) {
          return;
        }

        setErrorMessage(error instanceof Error ? error.message : '공유 리소스 토큰을 불러오지 못했습니다.');
      } finally {
        if (active) {
          setIsLoading(false);
        }
      }
    };

    refreshRef.current = refresh;
    void refresh();

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

    window.addEventListener(SHARED_RESOURCE_TOKENS_SYNC_EVENT, handleSync);

    return () => {
      active = false;
      window.removeEventListener(SHARED_RESOURCE_TOKENS_SYNC_EVENT, handleSync);
    };
  }, [shareToken]);

  return {
    tokens,
    isLoading,
    errorMessage,
    refresh: async () => {
      if (refreshRef.current) {
        await refreshRef.current();
      }
    },
  };
}
