import { appendClientIdHeader } from './clientIdentity';
import { getRegisteredAccessToken, isAllowedRegistrationToken } from './tokenAccess';

export type ResourceManagerEntryType = 'file' | 'directory';

export type ResourceManagerTreeNode = {
  name: string;
  path: string;
  type: ResourceManagerEntryType;
  extension: string | null;
  size: number | null;
  modifiedAt: string;
  previewUrl: string | null;
  children?: ResourceManagerTreeNode[];
};

export type ResourceManagerTreeRoot = {
  label: string;
  rootPath: string;
  tree: ResourceManagerTreeNode;
};

export type ResourceManagerDirectoryEntry = {
  name: string;
  path: string;
  type: ResourceManagerEntryType;
  extension: string | null;
  size: number | null;
  modifiedAt: string;
  previewUrl: string | null;
};

export type ResourceManagerDirectoryDetail = {
  path: string;
  items: ResourceManagerDirectoryEntry[];
};

export type ResourceManagerFileDetail = {
  name: string;
  path: string;
  extension: string | null;
  size: number;
  modifiedAt: string;
  mimeType: string;
  previewUrl: string;
  isTextEditable: boolean;
  content: string | null;
};

class ResourceManagerApiError extends Error {
  status: number;

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

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;
  const isLocalHost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0.0.0.0';

  if (!isLocalHost) {
    return null;
  }

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

const RESOURCE_MANAGER_API_BASE_URL = resolveApiBaseUrl();
const RESOURCE_MANAGER_API_FALLBACK_BASE_URL =
  !import.meta.env.VITE_WORK_SERVER_URL && RESOURCE_MANAGER_API_BASE_URL === '/api'
    ? resolveFallbackBaseUrl()
    : null;

function appendPreviewAccessToken(previewUrl: string | null) {
  if (!previewUrl) {
    return null;
  }

  const token = getRegisteredAccessToken();

  if (!token) {
    return previewUrl;
  }

  const separator = previewUrl.includes('?') ? '&' : '?';
  return `${previewUrl}${separator}token=${encodeURIComponent(token)}`;
}

function normalizeTreeNode(node: ResourceManagerTreeNode): ResourceManagerTreeNode {
  return {
    ...node,
    previewUrl: appendPreviewAccessToken(node.previewUrl),
    children: node.children?.map((child) => normalizeTreeNode(child)),
  };
}

async function requestOnce<T>(baseUrl: string, path: string, init?: RequestInit): Promise<T> {
  const headers = appendClientIdHeader(init?.headers);
  const hasBody = init?.body !== undefined && init.body !== null;
  const method = init?.method?.toUpperCase() ?? 'GET';
  const token = getRegisteredAccessToken();

  if (!isAllowedRegistrationToken(token)) {
    throw new ResourceManagerApiError('권한 토큰 등록 후에만 리소스 관리를 사용할 수 있습니다.', 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);
  }

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

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

    try {
      const payload = JSON.parse(text) as { message?: string };
      throw new ResourceManagerApiError(payload.message || '리소스 관리 요청에 실패했습니다.', response.status);
    } catch {
      throw new ResourceManagerApiError(text || '리소스 관리 요청에 실패했습니다.', response.status);
    }
  }

  return response.json() as Promise<T>;
}

async function request<T>(path: string, init?: RequestInit): Promise<T> {
  try {
    return await requestOnce<T>(RESOURCE_MANAGER_API_BASE_URL, path, init);
  } catch (error) {
    const shouldRetryWithFallback =
      RESOURCE_MANAGER_API_FALLBACK_BASE_URL &&
      RESOURCE_MANAGER_API_FALLBACK_BASE_URL !== RESOURCE_MANAGER_API_BASE_URL &&
      (error instanceof ResourceManagerApiError
        ? error.status === 404 || error.status === 408 || error.status === 502
        : error instanceof Error && /404|Failed to fetch|NetworkError/i.test(error.message));

    if (!shouldRetryWithFallback) {
      throw error;
    }

    return requestOnce<T>(RESOURCE_MANAGER_API_FALLBACK_BASE_URL, path, init);
  }
}

export async function fetchResourceManagerTree() {
  const response = await request<{ ok: boolean; item: ResourceManagerTreeRoot }>('/resource-manager/tree');
  return {
    ...response.item,
    tree: normalizeTreeNode(response.item.tree),
  };
}

export async function fetchResourceManagerDirectory(targetPath = '') {
  const query = new URLSearchParams({ path: targetPath });
  const response = await request<{ ok: boolean; item: ResourceManagerDirectoryDetail }>(
    `/resource-manager/directory?${query.toString()}`,
  );
  return {
    ...response.item,
    items: response.item.items.map((item) => ({
      ...item,
      previewUrl: appendPreviewAccessToken(item.previewUrl),
    })),
  };
}

export async function fetchResourceManagerFile(targetPath: string) {
  const query = new URLSearchParams({ path: targetPath });
  const response = await request<{ ok: boolean; item: ResourceManagerFileDetail }>(`/resource-manager/file?${query.toString()}`);
  return {
    ...response.item,
    previewUrl: appendPreviewAccessToken(response.item.previewUrl) ?? response.item.previewUrl,
  };
}

export async function createResourceManagerDirectory(parentPath: string, name: string) {
  await request('/resource-manager/directories', {
    method: 'POST',
    body: JSON.stringify({ parentPath, name }),
  });
}

export async function createResourceManagerFile(parentPath: string, name: string, content = '') {
  await request('/resource-manager/files', {
    method: 'POST',
    body: JSON.stringify({ parentPath, name, content }),
  });
}

export async function saveResourceManagerFile(targetPath: string, content: string) {
  await request('/resource-manager/files/content', {
    method: 'PUT',
    body: JSON.stringify({ path: targetPath, content }),
  });
}

async function readFileAsBase64(file: File) {
  const buffer = await file.arrayBuffer();
  let binary = '';
  const bytes = new Uint8Array(buffer);

  bytes.forEach((byte) => {
    binary += String.fromCharCode(byte);
  });

  return btoa(binary);
}

export async function uploadResourceManagerFile(parentPath: string, file: File) {
  const contentBase64 = await readFileAsBase64(file);
  await request('/resource-manager/files/upload', {
    method: 'POST',
    body: JSON.stringify({
      parentPath,
      fileName: file.name,
      contentBase64,
    }),
  });
}

export async function copyResourceManagerItem(targetPath: string, targetDirectoryPath: string, nextName?: string) {
  await request('/resource-manager/items/copy', {
    method: 'POST',
    body: JSON.stringify({
      path: targetPath,
      targetDirectoryPath,
      nextName: nextName?.trim() || null,
    }),
  });
}

export async function moveResourceManagerItem(targetPath: string, targetDirectoryPath: string, nextName?: string) {
  await request('/resource-manager/items/move', {
    method: 'POST',
    body: JSON.stringify({
      path: targetPath,
      targetDirectoryPath,
      nextName: nextName?.trim() || null,
    }),
  });
}

export async function deleteResourceManagerItem(targetPath: string) {
  const query = new URLSearchParams({ path: targetPath });
  await request(`/resource-manager/items?${query.toString()}`, {
    method: 'DELETE',
  });
}
