import type {
  ApiEnvelope,
  AdminUser,
  AccessQuery,
  AccessSummary,
  AccessThresholdConfig,
  AccessThresholdUpdate,
  AccessUnresolvedIdentity,
  AccessUnresolvedIdentityQuery,
  AccessVehicleRow,
  AlertAction,
  AlertEvent,
  AlertNotification,
  AlertQuery,
  AlertRule,
  AlertRuleInput,
  AlertSummary,
  DailyMileageRow,
  DashboardSummary,
  HistoryLocationRow,
  HistoryDataResponse,
  HistorySeriesResponse,
  HistoryExportJob,
  HistoryExportRequest,
  HistoryMetricCatalog,
  MetricCatalog,
  LatestTelemetryResponse,
  MileageSummary,
  MileageStatistics,
  MapReverseGeocode,
  MonitorMapResponse,
  MonitorWorkspaceResponse,
  MonitorSummary,
  OnlineStatisticsSummary,
  OnlineVehicleStatusRow,
  OpsHealth,
  QualityNotificationPlan,
  Page,
  QualitySummary,
  QualityIssueRow,
  RawFrameRow,
  RealtimeLocationRow,
  SourceReadinessPlan,
  SessionInfo,
  LoginResponse,
  CustomerUserInput,
  TrackPlaybackResponse,
  VehicleRealtimeRow,
  VehicleCoverageRow,
  VehicleCoverageSummary,
  VehicleDetail,
  VehicleProfile,
  VehicleProfileInput,
  VehicleProfileSyncRequest,
  VehicleProfileSyncResult,
  VehicleIdentityResolution,
  VehicleServiceOverview,
  VehicleServiceSummary,
  VehicleSourceDiagnostic,
  VehicleSourceEvidence,
  VehicleSourcePolicyUpdate,
  VehicleRow
} from './types';
import { getAccessToken, notifyUnauthorizedSession } from '../v2/auth/session';

export type RawFrameQuery = {
  keyword?: string;
  vin?: string;
  protocol?: string;
  dateFrom?: string;
  dateTo?: string;
  fields?: string[];
  includeFields?: boolean;
  limit?: number;
  offset?: number;
};

export type VehicleOverviewBatchQuery = {
  keywords: string[];
  protocol?: string;
  limit?: number;
  offset?: number;
};

type ApiErrorEnvelope = {
  error?: {
    code?: string;
    message?: string;
    detail?: string;
  };
  traceId?: string;
};

export const API_QUERY_TIMEOUT_MS = 15_000;

function queryTimeout(init: RequestInit | undefined) {
  const upstream = init?.signal;
  const controller = new AbortController();
  let timedOut = false;
  const abortFromUpstream = () => controller.abort(upstream?.reason);
  if (upstream?.aborted) abortFromUpstream();
  else upstream?.addEventListener('abort', abortFromUpstream, { once: true });
  const timer = window.setTimeout(() => {
    timedOut = true;
    controller.abort();
  }, API_QUERY_TIMEOUT_MS);
  return {
    init: { ...init, signal: controller.signal },
    timedOut: () => timedOut,
    cleanup: () => {
      window.clearTimeout(timer);
      upstream?.removeEventListener('abort', abortFromUpstream);
    }
  };
}

function requestTimeoutError() {
  const error = new Error(`请求超过 ${API_QUERY_TIMEOUT_MS / 1_000} 秒仍未完成，请检查网络后重试`);
  error.name = 'ApiRequestTimeoutError';
  return error;
}

async function request<T>(path: string, init?: RequestInit): Promise<T> {
  const token = getAccessToken();
  const requestInit = token ? { ...init, headers: withAuthorization(init?.headers, token) } : init;
  const timeout = queryTimeout(requestInit);
  try {
    const response = await fetch(path, timeout?.init ?? requestInit);
    if (!response.ok) {
      if (response.status === 401 && token && path !== '/api/v2/session') notifyUnauthorizedSession(token);
      throw new Error(await responseErrorMessage(response));
    }
    const envelope = (await response.json()) as ApiEnvelope<T>;
    if (timeout?.timedOut()) throw requestTimeoutError();
    return envelope.data;
  } catch (error) {
    if (timeout?.timedOut() && (!(error instanceof Error) || error.name !== 'ApiRequestTimeoutError')) {
      throw requestTimeoutError();
    }
    throw error;
  } finally {
    timeout?.cleanup();
  }
}

async function requestBlob(path: string, signal?: AbortSignal) {
  const token = getAccessToken();
  const init: RequestInit = token ? { signal, headers: withAuthorization(undefined, token) } : { signal };
  const response = await fetch(path, init);
  if (!response.ok) {
    if (response.status === 401 && token) notifyUnauthorizedSession(token);
    throw new Error(await responseErrorMessage(response));
  }
  const encodedFilename = response.headers.get('X-Export-Name')?.trim();
  let filename = 'history-export.csv';
  if (encodedFilename) {
    try {
      filename = decodeURIComponent(encodedFilename);
    } catch {
      filename = encodedFilename;
    }
  }
  return {
    blob: await response.blob(),
    filename
  };
}

function withAuthorization(headers: HeadersInit | undefined, token: string) {
  const authorized = new Headers(headers);
  authorized.set('Authorization', `Bearer ${token}`);
  return authorized;
}

function withSignal(init: RequestInit | undefined, signal?: AbortSignal) {
  return signal ? { ...init, signal } : init;
}

async function responseErrorMessage(response: Response) {
  try {
    const envelope = (await response.json()) as ApiErrorEnvelope;
    const message = envelope.error?.message?.trim();
    const detail = envelope.error?.detail?.trim();
    const traceID = envelope.traceId?.trim();
    if (message && detail) {
      return withTraceID(`${message}: ${detail}`, traceID);
    }
    if (message) {
      return withTraceID(message, traceID);
    }
  } catch {
    // Fall back to status when the server does not return the platform envelope.
  }
  return `request failed ${response.status}`;
}

function withTraceID(message: string, traceID?: string) {
  return traceID ? `${message} (traceId: ${traceID})` : message;
}

export const api = {
  session: (signal?: AbortSignal) => request<SessionInfo>('/api/v2/session', withSignal(undefined, signal)),
  login: (credentials: { username: string; password: string }) => request<LoginResponse>('/api/v2/auth/login', {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials)
  }),
  logout: () => request<{ loggedOut: boolean }>('/api/v2/auth/logout', { method: 'POST' }),
  changePassword: (input: { currentPassword: string; newPassword: string }) => request<{ changed: boolean }>('/api/v2/auth/password', {
    method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
  }),
  adminUsers: (signal?: AbortSignal) => request<AdminUser[]>('/api/v2/admin/users', withSignal(undefined, signal)),
  createCustomerUser: (input: CustomerUserInput) => request<{ id: number }>('/api/v2/admin/users', {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
  }),
  updateCustomerUser: (id: number, input: CustomerUserInput) => request<{ id: number }>(`/api/v2/admin/users/${id}`, {
    method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
  }),
  monitorSummary: (params = new URLSearchParams(), signal?: AbortSignal) => request<MonitorSummary>(`/api/v2/monitor/summary?${params.toString()}`, signal ? { signal } : undefined),
  monitorMap: (params = new URLSearchParams(), signal?: AbortSignal) => request<MonitorMapResponse>(`/api/v2/monitor/map?${params.toString()}`, signal ? { signal } : undefined),
  monitorWorkspace: (params = new URLSearchParams(), signal?: AbortSignal) => request<MonitorWorkspaceResponse>(`/api/v2/monitor/workspace?${params.toString()}`, signal ? { signal } : undefined),
  trackPlayback: (params = new URLSearchParams(), signal?: AbortSignal) => request<TrackPlaybackResponse>(`/api/v2/tracks?${params.toString()}`, signal ? { signal } : undefined),
  metricCatalog: (signal?: AbortSignal) => request<MetricCatalog>('/api/v2/metrics', withSignal(undefined, signal)),
  historyMetricCatalog: (signal?: AbortSignal) => request<HistoryMetricCatalog>('/api/v2/history/metrics', withSignal(undefined, signal)),
  historyData: (params = new URLSearchParams(), signal?: AbortSignal) => request<HistoryDataResponse>(`/api/v2/history/query?${params.toString()}`, signal ? { signal } : undefined),
  historySeries: (params = new URLSearchParams(), signal?: AbortSignal) => request<HistorySeriesResponse>(`/api/v2/history/series?${params.toString()}`, signal ? { signal } : undefined),
  historyExports: (signal?: AbortSignal) => request<HistoryExportJob[]>('/api/v2/exports', withSignal(undefined, signal)),
  createHistoryExport: (query: HistoryExportRequest) => request<HistoryExportJob>('/api/v2/exports', {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
  }),
  downloadHistoryExport: (id: string, signal?: AbortSignal) => requestBlob(`/api/v2/exports/${encodeURIComponent(id)}/download`, signal),
  accessSummary: (query: AccessQuery, signal?: AbortSignal) => request<AccessSummary>('/api/v2/access/summary', withSignal({
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
  }, signal)),
  accessVehicles: (query: AccessQuery, signal?: AbortSignal) => request<Page<AccessVehicleRow>>('/api/v2/access/vehicles', withSignal({
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
  }, signal)),
  accessUnresolvedIdentities: (query: AccessUnresolvedIdentityQuery, signal?: AbortSignal) => request<Page<AccessUnresolvedIdentity>>('/api/v2/access/unresolved-identities', withSignal({
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
  }, signal)),
  accessThresholds: (signal?: AbortSignal) => request<AccessThresholdConfig>('/api/v2/access/thresholds', withSignal(undefined, signal)),
  updateAccessThresholds: (update: AccessThresholdUpdate) => request<AccessThresholdConfig>('/api/v2/access/thresholds', {
    method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(update)
  }),
  alertSummaryV2: (query: AlertQuery, signal?: AbortSignal) => request<AlertSummary>('/api/v2/alerts/summary', withSignal({
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
  }, signal)),
  alertEventsV2: (query: AlertQuery, signal?: AbortSignal) => request<Page<AlertEvent>>('/api/v2/alerts/events', withSignal({
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
  }, signal)),
  alertEventV2: (id: string, signal?: AbortSignal) => request<AlertEvent>(`/api/v2/alerts/events/${encodeURIComponent(id)}`, withSignal(undefined, signal)),
  actOnAlertV2: (id: string, action: Pick<AlertAction, 'action' | 'note'> & { version: number; actor?: string }) => request<AlertEvent>(`/api/v2/alerts/events/${encodeURIComponent(id)}/actions`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(action)
  }),
  alertRulesV2: (signal?: AbortSignal) => request<AlertRule[]>('/api/v2/alerts/rules', withSignal(undefined, signal)),
  saveAlertRuleV2: (input: AlertRuleInput) => request<AlertRule>(input.version > 0 ? `/api/v2/alerts/rules/${encodeURIComponent(input.id)}` : '/api/v2/alerts/rules', {
    method: input.version > 0 ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
  }),
  setAlertRuleEnabledV2: (id: string, update: { version: number; enabled: boolean; actor?: string }) => request<AlertRule>(`/api/v2/alerts/rules/${encodeURIComponent(id)}/enabled`, {
    method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(update)
  }),
  alertNotificationsV2: (params = new URLSearchParams(), signal?: AbortSignal) => request<Page<AlertNotification>>(`/api/v2/alerts/notifications?${params.toString()}`, withSignal(undefined, signal)),
  readAlertNotificationsV2: (ids: number[]) => request<{ updated: number }>('/api/v2/alerts/notifications/read', {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids })
  }),
  dashboardSummary: () => request<DashboardSummary>('/api/dashboard/summary'),
  vehicles: (params = new URLSearchParams(), signal?: AbortSignal) => request<Page<VehicleRow>>(`/api/vehicles?${params.toString()}`, signal ? { signal } : undefined),
  vehicleResolve: (params = new URLSearchParams()) => request<VehicleIdentityResolution>(`/api/vehicles/resolve?${params.toString()}`),
  vehicleCoverage: (params = new URLSearchParams(), signal?: AbortSignal) => request<Page<VehicleCoverageRow>>(`/api/vehicles/coverage?${params.toString()}`, signal ? { signal } : undefined),
  vehicleCoverageSummary: (params = new URLSearchParams()) => request<VehicleCoverageSummary>(`/api/vehicles/coverage/summary?${params.toString()}`),
  vehicleDetail: (params = new URLSearchParams(), signal?: AbortSignal) => request<VehicleDetail>(`/api/vehicle-service?${params.toString()}`, withSignal(undefined, signal)),
  vehicleProfile: (vin: string) => request<VehicleProfile>(`/api/v2/vehicles/${encodeURIComponent(vin)}/profile`),
  latestTelemetry: (vin: string, signal?: AbortSignal) => request<LatestTelemetryResponse>(`/api/v2/vehicles/${encodeURIComponent(vin)}/telemetry/latest`, withSignal(undefined, signal)),
  vehicleSourceEvidence: (vin: string, date?: string, signal?: AbortSignal) => {
    const params = new URLSearchParams();
    if (date) params.set('date', date);
    const suffix = params.toString() ? `?${params.toString()}` : '';
    return request<VehicleSourceEvidence>(`/api/v2/vehicles/${encodeURIComponent(vin)}/source-evidence${suffix}`, withSignal(undefined, signal));
  },
  vehicleSourceDiagnostic: (vin: string, signal?: AbortSignal) => request<VehicleSourceDiagnostic>(
    `/api/v2/operations/vehicles/${encodeURIComponent(vin)}/sources`,
    withSignal(undefined, signal)
  ),
  updateVehicleSourcePolicy: (vin: string, update: VehicleSourcePolicyUpdate) => request<VehicleSourceDiagnostic>(
    `/api/v2/operations/vehicles/${encodeURIComponent(vin)}/sources/${encodeURIComponent(update.sourceRef)}`,
    {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        version: update.version,
        enabled: update.enabled,
        priority: update.priority,
        remark: update.remark
      })
    }
  ),
  updateVehicleProfile: (vin: string, input: VehicleProfileInput) => request<VehicleProfile>(`/api/v2/vehicles/${encodeURIComponent(vin)}/profile`, {
    method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
  }),
  syncVehicleProfiles: (input: VehicleProfileSyncRequest) => request<VehicleProfileSyncResult>('/api/v2/vehicle-profiles/sync', {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
  }),
  vehicleServiceSummary: () => request<VehicleServiceSummary>('/api/vehicle-service/summary'),
  vehicleServiceOverview: (params = new URLSearchParams()) => request<VehicleServiceOverview>(`/api/vehicle-service/overview?${params.toString()}`),
  vehicleServiceOverviews: (query: VehicleOverviewBatchQuery) => request<Page<VehicleServiceOverview>>('/api/vehicle-service/overviews', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(query)
  }),
  vehicleRealtime: (params = new URLSearchParams(), signal?: AbortSignal) => request<Page<VehicleRealtimeRow>>(`/api/realtime/vehicles?${params.toString()}`, signal ? { signal } : undefined),
  realtimeLocations: (params = new URLSearchParams()) => request<Page<RealtimeLocationRow>>(`/api/realtime/locations?${params.toString()}`),
  historyLocations: (params = new URLSearchParams()) => request<Page<HistoryLocationRow>>(`/api/history/locations?${params.toString()}`),
  rawFrames: (params = new URLSearchParams()) => request<Page<RawFrameRow>>(`/api/history/raw-frames?${params.toString()}`),
  rawFramesQuery: (query: RawFrameQuery) => request<Page<RawFrameRow>>('/api/history/raw-frames/query', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(query)
  }),
  mileageSummary: (params = new URLSearchParams()) => request<MileageSummary>(`/api/mileage/summary?${params.toString()}`),
  dailyMileage: (params = new URLSearchParams(), signal?: AbortSignal) => request<Page<DailyMileageRow>>(`/api/mileage/daily?${params.toString()}`, signal ? { signal } : undefined),
  mileageStatistics: (params = new URLSearchParams(), signal?: AbortSignal) => request<MileageStatistics>(`/api/v2/statistics/mileage?${params.toString()}`, signal ? { signal } : undefined),
  onlineStatisticsSummary: (params = new URLSearchParams()) => request<OnlineStatisticsSummary>(`/api/statistics/online-summary?${params.toString()}`),
  onlineVehicleStatuses: (params = new URLSearchParams()) => request<Page<OnlineVehicleStatusRow>>(`/api/statistics/online-vehicles?${params.toString()}`),
  qualitySummary: (params = new URLSearchParams()) => request<QualitySummary>(`/api/quality/summary?${params.toString()}`),
  qualityIssues: (params = new URLSearchParams()) => request<Page<QualityIssueRow>>(`/api/quality/issues?${params.toString()}`),
  qualityNotificationPlan: (params = new URLSearchParams()) => request<QualityNotificationPlan>(`/api/quality/notification-plan?${params.toString()}`),
  alertEventSummary: (params = new URLSearchParams()) => request<QualitySummary>(`/api/alert-events/summary?${params.toString()}`),
  alertEvents: (params = new URLSearchParams()) => request<Page<QualityIssueRow>>(`/api/alert-events?${params.toString()}`),
  alertEventNotificationPlan: (params = new URLSearchParams()) => request<QualityNotificationPlan>(`/api/alert-events/notification-plan?${params.toString()}`),
  reverseGeocode: (params = new URLSearchParams(), signal?: AbortSignal) => request<MapReverseGeocode>(`/api/map/reverse-geocode?${params.toString()}`, withSignal(undefined, signal)),
  opsHealth: (signal?: AbortSignal) => request<OpsHealth>('/api/ops/health', withSignal(undefined, signal)),
  sourceReadiness: (signal?: AbortSignal) => request<SourceReadinessPlan>('/api/ops/source-readiness', withSignal(undefined, signal))
};
