import { appendClientIdHeader } from '../../app/main/clientIdentity';
import { getRegisteredAccessToken } from '../../app/main/tokenAccess';

export type LayoutAxis = 'horizontal' | 'vertical';
export type SizeUnit = 'px' | '%';

export type SavedLayoutRecord = {
  id: string;
  name: string;
  createdAt: string;
  updatedAt: string;
  axis: LayoutAxis;
  sizeUnit: SizeUnit;
  primarySize: number;
  primaryMin: number;
  secondaryMin: number;
  resizable: boolean;
  selectedLeafId: string | null;
  totalPanes: number;
  summary: string;
  tree: unknown;
};

type SavedLayoutRow = {
  id: string;
  name: string;
  created_at: string;
  updated_at: string;
  axis: LayoutAxis;
  size_unit: SizeUnit;
  primary_size: number;
  primary_min: number;
  secondary_min: number;
  resizable: boolean;
  selected_leaf_id: string | null;
  total_panes: number;
  summary: string;
  tree: unknown;
};

const WORK_SERVER_TIMEOUT_MS = 8000;
const PLAY_LAYOUTS_TABLE = 'play_layouts';

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

function normalizeLayoutTimestamp(value: unknown, fallback: string) {
  if (typeof value === 'string') {
    const trimmed = value.trim();

    if (trimmed) {
      const parsed = Date.parse(trimmed);

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

  if (!isLocalWorkServerHost) {
    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 LayoutStorageError extends Error {
  status: number;

  constructor(message: string, status: number) {
    super(message);
    this.name = 'LayoutStorageError';
    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 LayoutStorageError('work-db 응답이 지연됩니다. 잠시 후 다시 시도해 주세요.', 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 the raw response text when JSON parsing is not available.
    }

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

  const contentType = response.headers.get('content-type') ?? '';

  if (!contentType.toLowerCase().includes('application/json')) {
    const text = await response.text();
    throw new LayoutStorageError(text ? 'work-db 응답이 JSON이 아닙니다.' : 'work-db 응답을 확인할 수 없습니다.', 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 shouldRetryWithFallback =
      WORK_SERVER_FALLBACK_BASE_URL &&
      WORK_SERVER_FALLBACK_BASE_URL !== WORK_SERVER_BASE_URL &&
      (error instanceof LayoutStorageError
        ? error.status === 404 || error.status === 408 || error.status === 502
        : error instanceof Error && (/not found/i.test(error.message) || /404/.test(error.message)));

    if (!shouldRetryWithFallback) {
      throw error;
    }

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

function toRecord(row: SavedLayoutRow): SavedLayoutRecord {
  const now = new Date().toISOString();

  return {
    id: row.id,
    name: row.name,
    createdAt: normalizeLayoutTimestamp(row.created_at, now),
    updatedAt: normalizeLayoutTimestamp(row.updated_at, now),
    axis: row.axis,
    sizeUnit: row.size_unit,
    primarySize: Number(row.primary_size),
    primaryMin: Number(row.primary_min),
    secondaryMin: Number(row.secondary_min),
    resizable: Boolean(row.resizable),
    selectedLeafId: row.selected_leaf_id,
    totalPanes: Number(row.total_panes),
    summary: row.summary,
    tree: row.tree,
  };
}

function toRow(record: SavedLayoutRecord): SavedLayoutRow {
  const now = new Date().toISOString();
  const createdAt = normalizeLayoutTimestamp(record.createdAt, now);
  const updatedAt = normalizeLayoutTimestamp(record.updatedAt, createdAt);

  return {
    id: record.id,
    name: record.name,
    created_at: createdAt,
    updated_at: updatedAt,
    axis: record.axis,
    size_unit: record.sizeUnit,
    primary_size: record.primarySize,
    primary_min: record.primaryMin,
    secondary_min: record.secondaryMin,
    resizable: record.resizable,
    selected_leaf_id: record.selectedLeafId,
    total_panes: record.totalPanes,
    summary: record.summary,
    tree: record.tree,
  };
}

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

      if (tableExists) {
        return;
      }

      try {
        await request<{ ok: boolean; tableName: string }>('/ddl/create-table', {
          method: 'POST',
          body: JSON.stringify({
            tableName: PLAY_LAYOUTS_TABLE,
            columns: [
              { name: 'id', type: 'text', nullable: false, primary: true },
              { name: 'name', 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: 'axis', type: 'text', nullable: false },
              { name: 'size_unit', type: 'text', nullable: false },
              { name: 'primary_size', type: 'integer', nullable: false },
              { name: 'primary_min', type: 'integer', nullable: false },
              { name: 'secondary_min', type: 'integer', nullable: false },
              { name: 'resizable', type: 'boolean', nullable: false, defaultTo: true },
              { name: 'selected_leaf_id', type: 'text', nullable: true },
              { name: 'total_panes', type: 'integer', nullable: false },
              { name: 'summary', type: 'text', nullable: false },
              { name: 'tree', type: 'jsonb', nullable: false },
            ],
          }),
        });
      } catch (error) {
        if (!(error instanceof LayoutStorageError) || !/already exists/i.test(error.message)) {
          throw error;
        }
      }
    })().catch((error) => {
      setupPromise = null;
      throw error;
    });
  }

  return setupPromise;
}

export async function listSavedLayouts() {
  await ensureLayoutStorageTable();

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

  return response.rows.map(toRecord);
}

export async function saveLayout(record: SavedLayoutRecord) {
  await ensureLayoutStorageTable();

  const row = toRow(record);
  const existing = await request<{ rows: SavedLayoutRow[] }>(`/crud/${PLAY_LAYOUTS_TABLE}/select`, {
    method: 'POST',
    body: JSON.stringify({
      where: [{ field: 'id', operator: 'eq', value: record.id }],
      limit: 1,
    }),
  });

  if (existing.rows.length > 0) {
    await request<{ ok: boolean }>(`/crud/${PLAY_LAYOUTS_TABLE}/update`, {
      method: 'PATCH',
      body: JSON.stringify({
        data: {
          name: row.name,
          updated_at: row.updated_at,
          axis: row.axis,
          size_unit: row.size_unit,
          primary_size: row.primary_size,
          primary_min: row.primary_min,
          secondary_min: row.secondary_min,
          resizable: row.resizable,
          selected_leaf_id: row.selected_leaf_id,
          total_panes: row.total_panes,
          summary: row.summary,
          tree: row.tree,
        },
        where: [{ field: 'id', operator: 'eq', value: record.id }],
      }),
    });
    return;
  }

  await request<{ ok: boolean }>(`/crud/${PLAY_LAYOUTS_TABLE}/insert`, {
    method: 'POST',
    body: JSON.stringify({
      data: row,
    }),
  });
}

export async function deleteLayout(id: string) {
  await ensureLayoutStorageTable();

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