import { appendClientIdHeader } from '../../../app/main/clientIdentity';
import { getRegisteredAccessToken } from '../../../app/main/tokenAccess';
import type { SavedLayoutDrawComponentRecord } from './layoutDrawTypes';
import { normalizeSavedLayoutDrawShapes, serializeSavedLayoutDrawShapes } from './layoutDrawStorageShapes.ts';

type SavedLayoutDrawComponentRow = {
  id: string;
  name: string;
  category: string;
  created_at: string;
  updated_at: string;
  shapes: SavedLayoutDrawComponentRecord['shapes'] | string;
};

const WORK_SERVER_TIMEOUT_MS = 8000;
const LAYOUT_DRAW_COMPONENT_TABLE = 'layout_draw_components';

let setupPromise: Promise<void> | null = null;

function normalizeTimestamp(value: unknown, fallback: string) {
  if (typeof value === 'string' && value.trim()) {
    const parsed = Date.parse(value);
    if (!Number.isNaN(parsed)) {
      return new Date(parsed).toISOString();
    }
  }

  return fallback;
}

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

  return '/api';
}

function resolveWorkServerFallbackBaseUrl() {
  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 WORK_SERVER_BASE_URL = resolveWorkServerBaseUrl();
const WORK_SERVER_FALLBACK_BASE_URL =
  !import.meta.env.VITE_WORK_SERVER_URL && WORK_SERVER_BASE_URL === '/api'
    ? resolveWorkServerFallbackBaseUrl()
    : null;

class LayoutDrawComponentStorageError extends Error {
  status: number;

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

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 controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), WORK_SERVER_TIMEOUT_MS);

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

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

  let response: Response;

  try {
    response = await fetch(`${baseUrl}${path}`, {
      ...init,
      headers,
      signal: controller.signal,
      cache: init?.cache ?? (method === 'GET' ? 'no-store' : undefined),
    });
  } catch (error) {
    clearTimeout(timeoutId);

    if (error instanceof DOMException && error.name === 'AbortError') {
      throw new LayoutDrawComponentStorageError('컴포넌트 저장소 응답이 지연됩니다. 잠시 후 다시 시도해 주세요.', 408);
    }

    throw error;
  }

  clearTimeout(timeoutId);

  if (!response.ok) {
    const text = await response.text();
    let message = text || '컴포넌트 저장소 요청에 실패했습니다.';

    try {
      const payload = JSON.parse(text) as { message?: string };
      message = payload.message || message;
    } catch {
      // Keep raw text.
    }

    throw new LayoutDrawComponentStorageError(message, response.status);
  }

  const contentType = response.headers.get('content-type') ?? '';
  if (!contentType.toLowerCase().includes('application/json')) {
    throw new LayoutDrawComponentStorageError('컴포넌트 저장소 응답이 JSON이 아닙니다.', 502);
  }

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

async function request<T>(path: string, init?: RequestInit): Promise<T> {
  try {
    return await requestOnce<T>(WORK_SERVER_BASE_URL, path, init);
  } catch (error) {
    const shouldRetry =
      WORK_SERVER_FALLBACK_BASE_URL &&
      WORK_SERVER_FALLBACK_BASE_URL !== WORK_SERVER_BASE_URL &&
      (error instanceof LayoutDrawComponentStorageError
        ? error.status === 404 || error.status === 408 || error.status === 502
        : error instanceof Error && (/not found/i.test(error.message) || /404/.test(error.message)));

    if (!shouldRetry) {
      throw error;
    }

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

function toRecord(row: SavedLayoutDrawComponentRow): SavedLayoutDrawComponentRecord {
  const now = new Date().toISOString();

  return {
    id: row.id,
    name: row.name,
    category: row.category,
    createdAt: normalizeTimestamp(row.created_at, now),
    updatedAt: normalizeTimestamp(row.updated_at, now),
    shapes: normalizeSavedLayoutDrawShapes(row.shapes).filter(
      (shape) => shape.type === 'line' || shape.type === 'rect',
    ) as SavedLayoutDrawComponentRecord['shapes'],
  };
}

async function ensureLayoutDrawComponentTable() {
  if (!setupPromise) {
    setupPromise = (async () => {
      const schemaResponse = await request<{ items: Array<{ table_name: string }> }>('/schema/tables');
      const tableExists = schemaResponse.items.some((item) => item.table_name === LAYOUT_DRAW_COMPONENT_TABLE);
      if (tableExists) {
        return;
      }

      try {
        await request<{ ok: boolean; tableName: string }>('/ddl/create-table', {
          method: 'POST',
          body: JSON.stringify({
            tableName: LAYOUT_DRAW_COMPONENT_TABLE,
            columns: [
              { name: 'id', type: 'text', nullable: false, primary: true },
              { name: 'name', type: 'text', nullable: false },
              { name: 'category', type: 'text', nullable: false },
              { name: 'created_at', type: 'timestamp with time zone', nullable: false },
              { name: 'updated_at', type: 'timestamp with time zone', nullable: false },
              { name: 'shapes', type: 'jsonb', nullable: false },
            ],
          }),
        });
      } catch (error) {
        if (!(error instanceof LayoutDrawComponentStorageError) || !/already exists/i.test(error.message)) {
          throw error;
        }
      }
    })().catch((error) => {
      setupPromise = null;
      throw error;
    });
  }

  return setupPromise;
}

export async function listSavedLayoutDrawComponents() {
  await ensureLayoutDrawComponentTable();

  const response = await request<{ rows: SavedLayoutDrawComponentRow[] }>(`/crud/${LAYOUT_DRAW_COMPONENT_TABLE}/select`, {
    method: 'POST',
    body: JSON.stringify({
      orderBy: [{ field: 'updated_at', direction: 'desc' }],
      limit: 200,
    }),
  });

  return response.rows.map(toRecord);
}

export async function saveLayoutDrawComponent(record: SavedLayoutDrawComponentRecord) {
  await ensureLayoutDrawComponentTable();

  await request<{ ok: boolean }>(`/crud/${LAYOUT_DRAW_COMPONENT_TABLE}/insert`, {
    method: 'POST',
    body: JSON.stringify({
      data: {
        id: record.id,
        name: record.name,
        category: record.category,
        created_at: record.createdAt,
        updated_at: record.updatedAt,
        shapes: serializeSavedLayoutDrawShapes(record.shapes),
      },
    }),
  });
}

export async function deleteLayoutDrawComponent(id: string) {
  await ensureLayoutDrawComponentTable();

  await request<{ ok: boolean }>(`/crud/${LAYOUT_DRAW_COMPONENT_TABLE}/delete`, {
    method: 'DELETE',
    body: JSON.stringify({
      where: [{ field: 'id', operator: 'eq', value: id }],
    }),
  });
}
