// SPDX-License-Identifier: GPL-3.0-or-later

// API client for the Private Archive client backend
// Backend runs on port 62774 by default

// The bundled desktop app (Tauri) serves the UI from tauri://localhost, so a
// relative path can't reach the local daemon — target it explicitly. In the
// browser (all-in-one container, same-origin) keep the relative path.
const isTauriRuntime =
  typeof window !== 'undefined' &&
  ('__TAURI_INTERNALS__' in window || '__TAURI__' in window);
const API_BASE = isTauriRuntime ? 'http://127.0.0.1:62774/api/v1' : '/api/v1';

// ============================================================
// Types matching the backend API
// ============================================================

export interface StatusResponse {
  status: string;
  version: string;
  uptime: string;
  timestamp: string;
  backend?: {
    registered: boolean;
    tor_connected: boolean;
    server_reachable: boolean;
  };
}

export interface TorStatusResponse {
  connected: boolean;
  circuit_count: number;
  bootstrap_pct: number;
  last_activity: string;
}

export interface ServerStatusResponse {
  connected: boolean;
  last_contact_at: string;
  account_status: string;
  credits_remaining: number;
}

export interface AccountResponse {
  cert_hash: string;
  registered_at: string;
  credits_remaining: number;
  storage_used_bytes: number;
  status: string;
}

export interface BackupRequest {
  paths: string[];
  exclude?: string[];
  tags?: Record<string, string>;
}

export interface RestoreRequest {
  snapshot_id: string;
  target: string;
  paths?: string[];
}

export interface Job {
  id: string;
  type: string;
  status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
  progress: number;
  created_at: string;
  started_at?: string;
  completed_at?: string;
  error?: string;
  paths?: string[];
  metadata?: Record<string, unknown>;
}

export interface Snapshot {
  id: string;
  time: string;
  hostname: string;
  paths: string[];
  tags?: Record<string, string>;
  size?: number;
}

export interface PathInfo {
  path: string;
  exists: boolean;
  is_dir: boolean;
  size_bytes?: number;
  file_count?: number;
  access_error?: string;
}

export interface PathsResponse {
  paths: PathInfo[];
  total_paths: number;
}

export interface ConfigResponse {
  backup_paths: string[];
  exclude_patterns: string[];
  server_address: string;
  tor_enabled: boolean;
  mode: string;
  auto_prune: boolean;
  missing_path_behavior: string;
}

export interface ConfigUpdateRequest {
  backup_paths?: string[];
  exclude_patterns?: string[];
  missing_path_behavior?: string;
}

export interface ModeResponse {
  mode: string;
  description: string;
  features: string[];
}

export interface RetentionResponse {
  keep_within_daily: string;
  keep_within_weekly: string;
  keep_within_monthly: string;
  keep_within_yearly: string;
  description: string;
}

export interface CredentialsResponse {
  access_key_id: string;
  bucket_name: string;
  has_credentials: boolean;
  data_backend: string;
}

export interface CredentialsRotateResponse {
  access_key_id: string;
  secret_access_key: string;
  bucket_name: string;
  rotated_at: string;
}

export interface ScheduleResponse {
  name: string;
  type: string;
  interval: string;
  enabled: boolean;
  last_run?: string;
  next_run?: string;
  metadata?: Record<string, unknown>;
}

export interface ScheduleRequest {
  type: string;
  interval: string;
  enabled?: boolean;
  metadata?: Record<string, unknown>;
}

export interface ErrorResponse {
  error: string;
  code?: string;
  details?: string;
}

// ============================================================
// API Error class
// ============================================================

export class APIError extends Error {
  code?: string;
  details?: string;
  status?: number;

  constructor(message: string, code?: string, details?: string, status?: number) {
    super(message);
    this.name = 'APIError';
    this.code = code;
    this.details = details;
    this.status = status;
  }
}

// ============================================================
// HTTP helpers
// ============================================================

async function request<T>(
  endpoint: string,
  options: RequestInit = {}
): Promise<T> {
  const url = `${API_BASE}${endpoint}`;

  const defaultHeaders: HeadersInit = {
    'Content-Type': 'application/json',
  };

  const response = await fetch(url, {
    ...options,
    headers: {
      ...defaultHeaders,
      ...options.headers,
    },
  });

  if (!response.ok) {
    let errorData: ErrorResponse;
    try {
      errorData = await response.json();
    } catch {
      throw new APIError(
        `HTTP ${response.status}: ${response.statusText}`,
        undefined,
        undefined,
        response.status,
      );
    }
    throw new APIError(errorData.error, errorData.code, errorData.details, response.status);
  }

  // Handle empty responses
  const text = await response.text();
  if (!text) {
    return {} as T;
  }

  return JSON.parse(text) as T;
}

// ============================================================
// Health
// ============================================================

export async function health(): Promise<{ status: string }> {
  return request('/health');
}

// ============================================================
// Status endpoints
// ============================================================

export async function getStatus(): Promise<StatusResponse> {
  return request('/status');
}

export async function getTorStatus(): Promise<TorStatusResponse> {
  return request('/status/tor');
}

export async function getServerStatus(): Promise<ServerStatusResponse> {
  return request('/status/server');
}

// ============================================================
// Account endpoints
// ============================================================

export async function getAccount(): Promise<AccountResponse> {
  return request('/account');
}

export async function register(serverAddress?: string): Promise<AccountResponse> {
  return request('/account/register', {
    method: 'POST',
    body: JSON.stringify({ server_address: serverAddress }),
  });
}

export interface PaymentIntentResponse {
  intent_id: string;
  credits_requested: number;
  amount_xmr: string;
  payment_address: string;
  expires_at: string;
  rate_locked: boolean;
}

export async function createPaymentIntent(creditsRequested: number): Promise<PaymentIntentResponse> {
  return request('/account/payment', {
    method: 'POST',
    body: JSON.stringify({ credits_requested: creditsRequested }),
  });
}

export interface LTCPaymentIntentResponse {
  intent_id: string;
  payment_address: string;
  ltc_zar_rate_locked: string;
  credit_price_zar: string;
  credits_requested: number;
  expected_amount_ltc: string; // exact LTC to send for credits_requested
  expires_at: string;
}

// Litecoin uses the rate-lock model: the intent locks an LTC/ZAR rate + a receive
// address (no fixed amount). The caller computes the LTC needed for N credits as
// N * credit_price_zar / ltc_zar_rate_locked. An opt-in fiat card on-ramp can then
// buy that LTC and pay it to payment_address.
export async function createLTCPaymentIntent(credits: number): Promise<LTCPaymentIntentResponse> {
  return request('/account/payment/ltc', {
    method: 'POST',
    body: JSON.stringify({ credits }),
  });
}

export interface LTCPaymentStatus {
  has_pending: boolean;
  intent_id?: string;
  credits_requested?: number;
  expected_amount_ltc?: string;
  received_amount_ltc?: string;
  remaining_amount_ltc?: string;
  remaining_amount_usd?: string;
  status?: 'awaiting' | 'underpaid' | 'sufficient';
}

// Polls how far the pending LTC intent has been paid, so we can tell the customer
// if they've underpaid and exactly how much more to send.
export async function getLTCPaymentStatus(): Promise<LTCPaymentStatus> {
  return request('/account/payment/ltc/status', { method: 'GET' });
}

// ============================================================
// Backup endpoints
// ============================================================

export async function startBackup(req: BackupRequest): Promise<Job> {
  return request('/backup/start', {
    method: 'POST',
    body: JSON.stringify(req),
  });
}

export async function getBackupStatus(): Promise<Job> {
  return request('/backup/status');
}

export async function cancelBackup(): Promise<void> {
  await request('/backup/cancel', { method: 'POST' });
}

// ============================================================
// Restore endpoints
// ============================================================

export async function startRestore(req: RestoreRequest): Promise<Job> {
  return request('/restore/start', {
    method: 'POST',
    body: JSON.stringify(req),
  });
}

export async function getRestoreStatus(): Promise<Job> {
  return request('/restore/status');
}

export async function cancelRestore(): Promise<void> {
  await request('/restore/cancel', { method: 'POST' });
}

// ============================================================
// Snapshot endpoints
// ============================================================

export async function listSnapshots(): Promise<Snapshot[]> {
  return request('/snapshots');
}

export async function getSnapshot(id: string): Promise<Snapshot> {
  return request(`/snapshots/${id}`);
}

export async function deleteSnapshot(id: string): Promise<void> {
  await request(`/snapshots/${id}`, { method: 'DELETE' });
}

// ============================================================
// Job endpoints
// ============================================================

export async function listJobs(status?: string): Promise<Job[]> {
  const query = status ? `?status=${status}` : '';
  return request(`/jobs${query}`);
}

export async function getJob(id: string): Promise<Job> {
  return request(`/jobs/${id}`);
}

export async function cancelJob(id: string): Promise<void> {
  await request(`/jobs/${id}/cancel`, { method: 'POST' });
}

// ============================================================
// Schedule endpoints
// ============================================================

export async function listSchedules(): Promise<Record<string, ScheduleResponse>> {
  return request('/schedules');
}

export async function createSchedule(name: string, schedule: ScheduleRequest): Promise<ScheduleResponse> {
  return request('/schedules', {
    method: 'POST',
    body: JSON.stringify({ ...schedule, name }),
  });
}

export async function getSchedule(name: string): Promise<ScheduleResponse> {
  return request(`/schedules/${encodeURIComponent(name)}`);
}

export async function updateSchedule(name: string, schedule: Partial<ScheduleRequest>): Promise<ScheduleResponse> {
  return request(`/schedules/${encodeURIComponent(name)}`, {
    method: 'PUT',
    body: JSON.stringify(schedule),
  });
}

export async function deleteSchedule(name: string): Promise<void> {
  await request(`/schedules/${encodeURIComponent(name)}`, { method: 'DELETE' });
}

export async function triggerSchedule(name: string): Promise<Job> {
  return request(`/schedules/${encodeURIComponent(name)}/trigger`, {
    method: 'POST',
  });
}

// ============================================================
// Config endpoints
// ============================================================

export async function getConfig(): Promise<ConfigResponse> {
  return request('/config');
}

export async function updateConfig(config: ConfigUpdateRequest): Promise<ConfigResponse> {
  return request('/config', {
    method: 'PUT',
    body: JSON.stringify(config),
  });
}

// ============================================================
// Mode endpoints
// ============================================================

export async function getMode(): Promise<ModeResponse> {
  return request('/mode');
}

export async function updateMode(mode: string, acknowledge?: boolean): Promise<ModeResponse> {
  return request('/mode', {
    method: 'PUT',
    body: JSON.stringify({ mode, acknowledge }),
  });
}

// ============================================================
// Path endpoints
// ============================================================

export async function getPaths(): Promise<PathsResponse> {
  return request('/paths');
}

export async function addPath(path: string): Promise<PathsResponse> {
  return request('/paths', {
    method: 'POST',
    body: JSON.stringify({ path }),
  });
}

export async function removePath(path: string): Promise<PathsResponse> {
  return request('/paths', {
    method: 'DELETE',
    body: JSON.stringify({ path }),
  });
}

// ============================================================
// Retention endpoint
// ============================================================

export async function getRetention(): Promise<RetentionResponse> {
  return request('/retention');
}

// ============================================================
// Credential endpoints
// ============================================================

export async function getCredentials(): Promise<CredentialsResponse> {
  return request('/credentials');
}

export async function rotateCredentials(): Promise<CredentialsRotateResponse> {
  return request('/credentials/rotate', { method: 'POST' });
}

// ============================================================
// Update endpoints
// ============================================================

export interface UpdateState {
  current_version: string;
  latest_version?: string;
  update_available: boolean;
  auto_update: boolean;
  channel: string;
  last_checked?: string;
  last_error?: string;
  pending_path?: string;
  release_notes_url?: string;
}

export interface UpdateConfig {
  auto_update: boolean;
  channel: string;
}

export async function getUpdateStatus(): Promise<UpdateState> {
  return request('/updates/status');
}

export async function checkForUpdates(install = false): Promise<UpdateState> {
  const qs = install ? '?install=true' : '';
  return request(`/updates/check${qs}`, { method: 'POST' });
}

export async function getUpdateConfig(): Promise<UpdateConfig> {
  return request('/updates/config');
}

export async function setAutoUpdate(enabled: boolean): Promise<UpdateConfig> {
  return request('/updates/config', {
    method: 'PUT',
    body: JSON.stringify({ auto_update: enabled }),
  });
}
