export const DEFAULT_PACKAGE_BASE_URL = normalizeUrl(
  import.meta.env.VITE_PACKAGE_BASE_URL || window.location.origin
);
export const DEFAULT_AGENT_BASE_URL = normalizeUrl(
  import.meta.env.VITE_AGENT_BASE_URL || 'http://127.0.0.1:5010'
);
export const DEFAULT_APP_OPEN_URL = normalizeUrl(
  import.meta.env.VITE_APP_OPEN_URL || 'http://127.0.0.1'
);

export function normalizeUrl(value) {
  const text = String(value || '').trim();
  return text.replace(/\/+$/, '');
}

export function joinUrl(baseUrl, path) {
  const normalizedBaseUrl = normalizeUrl(baseUrl);
  const normalizedPath = String(path || '').startsWith('/') ? path : `/${path || ''}`;
  return `${normalizedBaseUrl}${normalizedPath}`;
}

function normalizeOpenUrl(value) {
  const text = normalizeUrl(value);
  if (!text) return '';

  try {
    const parsed = new URL(text);
    return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? text : '';
  } catch {
    return '';
  }
}

export function getAppOpenUrl(app) {
  return normalizeOpenUrl(
    app?.openUrl
    || app?.open_url
    || app?.webUrl
    || app?.web_url
    || app?.homepageUrl
    || app?.homepage_url
    || app?.homepage
  ) || normalizeOpenUrl(DEFAULT_APP_OPEN_URL);
}

async function requestJson(baseUrl, path, options = {}) {
  const {
    timeoutMs = 8000,
    body,
    headers,
    ...fetchOptions
  } = options;
  const url = joinUrl(baseUrl, path);
  const controller = new AbortController();
  const timeout = window.setTimeout(() => controller.abort(), timeoutMs);

  try {
    const response = await fetch(url, {
      ...fetchOptions,
      headers: {
        Accept: 'application/json',
        ...(body ? { 'Content-Type': 'application/json' } : {}),
        ...headers
      },
      body: body ? JSON.stringify(body) : undefined,
      signal: controller.signal
    });

    const text = await response.text();
    let payload = null;
    if (text) {
      try {
        payload = JSON.parse(text);
      } catch {
        payload = text;
      }
    }

    if (!response.ok) {
      throw new Error(`${response.status} ${formatErrorDetail(payload || response.statusText)}`);
    }

    return payload;
  } catch (error) {
    if (error?.name === 'AbortError') {
      throw new Error(`Request timeout: ${url}`);
    }
    if (error instanceof TypeError) {
      throw new Error(`Cannot fetch ${url}. Check endpoint reachability and CORS for this Web Client origin.`);
    }
    throw error;
  } finally {
    window.clearTimeout(timeout);
  }
}

function formatErrorDetail(detail) {
  if (Array.isArray(detail)) {
    return detail.map(formatErrorDetail).filter(Boolean).join('; ');
  }

  if (detail && typeof detail === 'object') {
    const location = Array.isArray(detail.loc) ? detail.loc.join('.') : '';
    const messageParts = [
      detail.msg,
      detail.message,
      detail.error,
      detail.detail
    ]
      .map((item) => String(item || '').trim())
      .filter(Boolean);

    if (Array.isArray(detail.missingPackageFiles) && detail.missingPackageFiles.length > 0) {
      const missingFiles = detail.missingPackageFiles
        .map(formatMissingPackageFile)
        .filter(Boolean)
        .join('; ');

      if (missingFiles) {
        messageParts.push(`Missing package files: ${missingFiles}`);
      }
    }

    const message = [...new Set(messageParts)].join('. ');

    if (message) {
      return location ? `${location}: ${message}` : String(message);
    }

    try {
      return JSON.stringify(detail);
    } catch {
      return String(detail);
    }
  }

  return String(detail || 'Request failed');
}

function formatMissingPackageFile(item) {
  if (!item || typeof item !== 'object') return String(item || '').trim();

  const packageName = String(item.packageName || item.componentId || 'package').trim();
  const version = String(item.version || '').trim();
  const downloadUrl = String(item.downloadUrl || '').trim();
  const label = [packageName, version].filter(Boolean).join(' ');

  return downloadUrl ? `${label} (${downloadUrl})` : label;
}

export async function fetchPackageApps(packageBaseUrl) {
  const payload = await requestJson(packageBaseUrl, '/api/apps', { timeoutMs: 10000 });
  return Array.isArray(payload?.apps) ? payload.apps.map(normalizePackageApp) : [];
}

export async function fetchLatestAgentPackage(packageBaseUrl, arch = 'amd64') {
  const query = arch ? `?arch=${encodeURIComponent(arch)}` : '';
  return normalizeLatestAgentPackage(
    await requestJson(packageBaseUrl, `/api/agent/latest${query}`, { timeoutMs: 7000 })
  );
}

export async function fetchApplicationDetail(packageBaseUrl, appId) {
  return requestJson(packageBaseUrl, `/api/apps/${encodeURIComponent(appId)}`, { timeoutMs: 10000 });
}

export async function fetchApplicationManifest(packageBaseUrl, appId, version) {
  return requestJson(
    packageBaseUrl,
    `/api/apps/${encodeURIComponent(appId)}/versions/${encodeURIComponent(version)}/manifest`,
    { timeoutMs: 10000 }
  );
}

export async function fetchAgentHealth(agentBaseUrl) {
  return requestJson(agentBaseUrl, '/health', { timeoutMs: 2800 });
}

export async function fetchAgentSystemInfo(agentBaseUrl) {
  return requestJson(agentBaseUrl, '/system-info', { timeoutMs: 5000 });
}

export async function fetchInstalledApps(agentBaseUrl) {
  const payload = await requestJson(agentBaseUrl, '/apps/installed', { timeoutMs: 20000 });
  return Array.isArray(payload) ? payload.map(normalizeInstalledApp) : [];
}

export async function queueInstall(agentBaseUrl, app) {
  return requestJson(agentBaseUrl, '/apps/install', {
    method: 'POST',
    timeoutMs: 10000,
    body: {
      appId: app.appId,
      appName: app.appName,
      version: app.version
    }
  });
}

export async function queueUpdate(agentBaseUrl, app, installedApp) {
  return requestJson(agentBaseUrl, '/apps/update', {
    method: 'POST',
    timeoutMs: 10000,
    body: {
      appId: app.appId,
      appName: app.appName,
      currentVersion: installedApp?.version || '',
      targetVersion: app.version
    }
  });
}

export async function queueRemove(agentBaseUrl, app) {
  return requestJson(agentBaseUrl, '/apps/remove', {
    method: 'POST',
    timeoutMs: 10000,
    body: {
      appId: app.appId,
      purge: true
    }
  });
}

export async function fetchTaskStatus(agentBaseUrl, taskId) {
  return normalizeTask(await requestJson(agentBaseUrl, `/tasks/${encodeURIComponent(taskId)}`, { timeoutMs: 7000 }));
}

export async function fetchTaskLogs(agentBaseUrl, taskId) {
  const payload = await requestJson(agentBaseUrl, `/tasks/${encodeURIComponent(taskId)}/logs`, { timeoutMs: 7000 });
  return Array.isArray(payload?.logs) ? payload.logs.map(normalizeLog) : [];
}

export async function fetchTaskComponents(agentBaseUrl, taskId) {
  const payload = await requestJson(agentBaseUrl, `/tasks/${encodeURIComponent(taskId)}/components`, { timeoutMs: 7000 });
  return Array.isArray(payload?.components) ? payload.components.map(normalizeComponent) : [];
}

function normalizePackageApp(app) {
  return {
    appId: String(app.appId || app.app_id || app.id || '').trim(),
    appCode: String(app.appCode || app.app_code || app.code || app.appId || app.app_id || '').trim(),
    appName: String(app.appName || app.app_name || app.name || '').trim(),
    version: String(app.version || '').trim(),
    status: String(app.status || 'Released').trim(),
    packageCount: Number(app.packageCount || app.package_count || 0),
    openUrl: normalizeOpenUrl(
      app.openUrl
      || app.open_url
      || app.webUrl
      || app.web_url
      || app.homepageUrl
      || app.homepage_url
      || app.homepage
    )
  };
}

function normalizeInstalledApp(app) {
  const serviceChecks = normalizeServiceChecks(app.serviceChecks || app.service_checks);
  return {
    appId: String(app.appId || app.app_id || '').trim(),
    appName: String(app.appName || app.app_name || '').trim(),
    version: String(app.installedVersion || app.version || app.package_version || '').trim(),
    status: String(app.status || 'installed').trim(),
    installedAt: app.installedAt || app.installed_at || '',
    updatedAt: app.updatedAt || app.updated_at || '',
    serviceCheckStatus: String(
      app.serviceCheckStatus
      || app.service_check_status
      || deriveServiceCheckStatus(serviceChecks)
    ).trim(),
    serviceChecks,
    openUrl: normalizeOpenUrl(
      app.openUrl
      || app.open_url
      || app.webUrl
      || app.web_url
      || app.homepageUrl
      || app.homepage_url
      || app.homepage
    )
  };
}

function normalizeLatestAgentPackage(agentPackage) {
  return {
    version: String(agentPackage?.version || '').trim(),
    arch: String(agentPackage?.arch || '').trim(),
    fileName: String(agentPackage?.fileName || agentPackage?.file_name || '').trim(),
    sizeLabel: String(agentPackage?.sizeLabel || agentPackage?.size_label || '').trim(),
    downloadUrl: String(agentPackage?.downloadUrl || agentPackage?.download_url || '').trim(),
    installCommand: String(agentPackage?.installCommand || agentPackage?.install_command || '').trim()
  };
}

function normalizeTask(task) {
  return {
    taskId: task.taskId || task.task_id,
    type: task.type,
    appId: task.appId || task.app_id,
    appName: task.appName || task.app_name,
    status: task.status,
    progress: Number(task.progress || 0),
    currentStep: task.currentStep || task.current_step,
    currentComponentId: task.currentComponentId || task.current_component_id,
    errorMessage: task.errorMessage || task.error_message,
    createdAt: task.createdAt || task.created_at,
    startedAt: task.startedAt || task.started_at,
    finishedAt: task.finishedAt || task.finished_at
  };
}

function normalizeLog(log) {
  return {
    time: log.time || log.timestamp || '',
    level: log.level || 'info',
    message: log.message || ''
  };
}

function normalizeComponent(component) {
  const serviceChecks = normalizeServiceChecks(component.serviceChecks || component.service_checks);
  return {
    componentId: component.componentId || component.component_id,
    type: component.type,
    status: component.status,
    progress: Number(component.progress || 0),
    currentStep: component.currentStep || component.current_step,
    errorMessage: component.errorMessage || component.error_message,
    serviceCheckStatus: String(
      component.serviceCheckStatus
      || component.service_check_status
      || deriveServiceCheckStatus(serviceChecks)
    ).trim(),
    serviceChecks,
    startedAt: component.startedAt || component.started_at,
    finishedAt: component.finishedAt || component.finished_at
  };
}

function normalizeServiceChecks(value) {
  return Array.isArray(value) ? value.map(normalizeServiceCheck).filter((item) => item.serviceName) : [];
}

function normalizeServiceCheck(check) {
  const activeState = String(check?.activeState || check?.active_state || check?.status || 'unknown').trim();
  const active = typeof check?.active === 'boolean' ? check.active : activeState === 'active';
  const readinessStatus = String(check?.readinessStatus || check?.readiness_status || '').trim();
  const healthy = typeof check?.healthy === 'boolean'
    ? check.healthy
    : active && (!readinessStatus || readinessStatus === 'ready');

  return {
    serviceName: String(check?.serviceName || check?.service_name || '').trim(),
    componentId: String(check?.componentId || check?.component_id || '').trim(),
    packageName: String(check?.packageName || check?.package_name || '').trim(),
    loadState: String(check?.loadState || check?.load_state || 'unknown').trim(),
    activeState,
    subState: String(check?.subState || check?.sub_state || 'unknown').trim(),
    unitFileState: String(check?.unitFileState || check?.unit_file_state || 'unknown').trim(),
    readinessType: String(check?.readinessType || check?.readiness_type || '').trim(),
    readinessStatus,
    status: String(check?.status || activeState || 'unknown').trim(),
    active,
    enabled: Boolean(check?.enabled),
    healthy,
    stale: Boolean(check?.stale),
    checkedAt: check?.checkedAt || check?.checked_at || '',
    errorMessage: check?.errorMessage || check?.error_message || ''
  };
}

function deriveServiceCheckStatus(serviceChecks) {
  if (!serviceChecks.length) return 'not-checked';
  return serviceChecks.every((check) => check.healthy) ? 'healthy' : 'unhealthy';
}
