import { createQuery } from '@tanstack/svelte-query';
import { api } from './client';
import { qk } from './queries';
import type {
  ActionResult,
  AdminPlaybackError,
  AdminQuarantineRow,
  LidarrConfig,
  LidarrMetadataProfile,
  LidarrQualityProfile,
  LidarrQuarantineActionRow,
  LidarrRequest,
  LidarrRequestStatus,
  LidarrRootFolder,
  LidarrTestResult,
  PlaybackErrorResolution
} from './types';

// Admin Lidarr config -----------------------------------------------------

export async function getLidarrConfig(): Promise<LidarrConfig> {
  return api.get<LidarrConfig>('/api/admin/lidarr/config');
}

export async function putLidarrConfig(cfg: LidarrConfig): Promise<LidarrConfig> {
  return api.put<LidarrConfig>('/api/admin/lidarr/config', cfg);
}

// testLidarrConnection always returns 200 with a discriminated-union body —
// callers branch on `result.ok`. We do NOT throw on `ok:false`; the SPA wants
// to render either branch (e.g. "connected to Lidarr X.Y.Z" vs "auth failed").
export async function testLidarrConnection(
  body: { base_url?: string; api_key?: string } = {}
): Promise<LidarrTestResult> {
  return api.post<LidarrTestResult>('/api/admin/lidarr/test', body);
}

export async function listQualityProfiles(): Promise<LidarrQualityProfile[]> {
  return api.get<LidarrQualityProfile[]>('/api/admin/lidarr/quality-profiles');
}

export async function listMetadataProfiles(): Promise<LidarrMetadataProfile[]> {
  return api.get<LidarrMetadataProfile[]>('/api/admin/lidarr/metadata-profiles');
}

export async function listRootFolders(): Promise<LidarrRootFolder[]> {
  return api.get<LidarrRootFolder[]>('/api/admin/lidarr/root-folders');
}

// Admin request queue -----------------------------------------------------

export async function listAdminRequests(
  status?: LidarrRequestStatus,
  limit?: number
): Promise<LidarrRequest[]> {
  const params = new URLSearchParams();
  if (status) params.set('status', status);
  if (limit !== undefined) params.set('limit', String(limit));
  const qs = params.toString();
  return api.get<LidarrRequest[]>(
    qs ? `/api/admin/requests?${qs}` : '/api/admin/requests'
  );
}

export async function approveRequest(
  id: string,
  overrides: { quality_profile_id?: number; root_folder_path?: string } = {}
): Promise<LidarrRequest> {
  return api.post<LidarrRequest>(`/api/admin/requests/${id}/approve`, overrides);
}

export async function rejectRequest(
  id: string,
  notes?: string
): Promise<LidarrRequest> {
  const body = notes !== undefined ? { notes } : {};
  return api.post<LidarrRequest>(`/api/admin/requests/${id}/reject`, body);
}

// Query factories ---------------------------------------------------------

export function createLidarrConfigQuery() {
  return createQuery({
    queryKey: qk.lidarrConfig(),
    queryFn: getLidarrConfig,
    staleTime: 60_000
  });
}

// `enabled` is passed in by the caller — typically derived from
// LidarrConfig.enabled — so the query only fires once Lidarr is configured.
// Keeping it as a prop (vs. reading config inside this factory) preserves
// purity and lets the caller choose its own gating logic.
export function createQualityProfilesQuery(enabled: boolean = true) {
  return createQuery({
    queryKey: qk.lidarrQualityProfiles(),
    queryFn: listQualityProfiles,
    enabled
  });
}

export function createMetadataProfilesQuery(enabled: boolean = true) {
  return createQuery({
    queryKey: qk.lidarrMetadataProfiles(),
    queryFn: listMetadataProfiles,
    enabled
  });
}

export function createRootFoldersQuery(enabled: boolean = true) {
  return createQuery({
    queryKey: qk.lidarrRootFolders(),
    queryFn: listRootFolders,
    enabled
  });
}

export function createAdminRequestsQuery(status?: LidarrRequestStatus) {
  return createQuery({
    queryKey: qk.adminRequests(status),
    queryFn: () => listAdminRequests(status),
    // Only the 'approved' tab needs polling — that's where in-flight
    // ingests live. Other tabs (pending/rejected/completed) are static
    // until the operator acts on them.
    refetchInterval: (query) => {
      if (status !== 'approved') return false;
      const rows = query.state.data as LidarrRequest[] | undefined;
      return hasInFlightRequest(rows) ? 12_000 : false;
    }
  });
}

function hasInFlightRequest(rows: readonly LidarrRequest[] | undefined): boolean {
  return rows?.some((r) => r.status === 'approved') ?? false;
}

// Admin quarantine --------------------------------------------------------

export async function listAdminQuarantine(): Promise<AdminQuarantineRow[]> {
  return api.get<AdminQuarantineRow[]>('/api/admin/quarantine');
}

export async function resolveQuarantine(trackID: string): Promise<ActionResult> {
  return api.post<ActionResult>(`/api/admin/quarantine/${trackID}/resolve`, {});
}

export async function deleteQuarantineFile(trackID: string): Promise<ActionResult> {
  return api.post<ActionResult>(`/api/admin/quarantine/${trackID}/delete-file`, {});
}

export async function deleteQuarantineViaLidarr(trackID: string): Promise<ActionResult> {
  return api.post<ActionResult>(`/api/admin/quarantine/${trackID}/delete-via-lidarr`, {});
}

export async function listQuarantineActions(
  limit: number = 50
): Promise<LidarrQuarantineActionRow[]> {
  return api.get<LidarrQuarantineActionRow[]>(
    `/api/admin/quarantine/actions?limit=${limit}`
  );
}

export function createAdminQuarantineQuery() {
  return createQuery({
    queryKey: qk.adminQuarantine(),
    queryFn: listAdminQuarantine,
    staleTime: 30_000
  });
}

export function createQuarantineActionsQuery(limit: number = 50) {
  return createQuery({
    queryKey: qk.adminQuarantineActions(limit),
    queryFn: () => listQuarantineActions(limit),
    staleTime: 60_000
  });
}

// Admin playback errors ---------------------------------------------------

export async function listAdminPlaybackErrors(
  resolved: boolean = false
): Promise<AdminPlaybackError[]> {
  return api.get<AdminPlaybackError[]>(
    `/api/admin/playback-errors?resolved=${resolved}`
  );
}

export async function resolvePlaybackError(
  id: string,
  resolution: PlaybackErrorResolution
): Promise<{ id: string }> {
  return api.post<{ id: string }>(
    `/api/admin/playback-errors/${id}/resolve`,
    { resolution }
  );
}

export function createAdminPlaybackErrorsQuery(resolved: boolean = false) {
  return createQuery({
    queryKey: qk.adminPlaybackErrors(resolved),
    queryFn: () => listAdminPlaybackErrors(resolved),
    staleTime: 30_000
  });
}

// Admin cover art ---------------------------------------------------------

export type RefetchAlbumCoverResponse = {
  album_id: string;
  cover_art_path: string | null;
  cover_art_source: string | null;
};

export async function refetchAlbumCover(albumId: string): Promise<RefetchAlbumCoverResponse> {
  return api.post<RefetchAlbumCoverResponse>(`/api/admin/albums/${albumId}/cover/refetch`, {});
}

export type RefetchMissingResponse = {
  started: boolean;
};

export async function refetchMissingCovers(): Promise<RefetchMissingResponse> {
  return api.post<RefetchMissingResponse>('/api/admin/covers/refetch-missing', {});
}

// Library scan -------------------------------------------------------------

export type ScanStageLibrary = {
  scanned: number;
  added: number;
  updated: number;
  skipped: number;
  errored: number;
};

export type ScanStageMbidBackfill = {
  processed: number;
  healed: number;
  skipped: number;
  duplicates?: number;
};

export type ScanStageCoverEnrich = {
  processed: number;
  succeeded: number;
  failed: number;
};

export type ScanStageArtistArtEnrich = {
  processed: number;
  succeeded: number;
  failed: number;
};

export type ScanStatus = {
  id: string;
  started_at: string;
  finished_at: string | null;
  library?: ScanStageLibrary;
  mbid_backfill?: ScanStageMbidBackfill;
  cover_enrich?: ScanStageCoverEnrich;
  artist_art_enrich?: ScanStageArtistArtEnrich;
  error_message?: string;
  in_flight: boolean;
};

export async function getScanStatus(): Promise<ScanStatus> {
  return api.get<ScanStatus>('/api/admin/scan/status');
}

export type TriggerScanResp = { id?: string };

export async function triggerScan(): Promise<TriggerScanResp> {
  return api.post<TriggerScanResp>('/api/admin/scan/run', {});
}

// Polls every 3s while a scan is in flight; falls back to a 30s stale-time
// when idle so the section auto-updates as workers complete each stage.
export function createScanStatusQuery() {
  return createQuery({
    queryKey: qk.scanStatus(),
    queryFn: getScanStatus,
    staleTime: 30_000,
    refetchInterval: 3_000
  });
}

// Library coverage --------------------------------------------------------

export type CoverageRollup = {
  total: number;
  with_art: number;
  pending: number;
  settled: number;
  pending_no_mbid: number;
};

export async function getCoverageRollup(): Promise<CoverageRollup> {
  return api.get<CoverageRollup>('/api/admin/library/coverage');
}

// Same pacing as scan-status — 3s while polling, 30s stale-time. The
// query is cheap server-side (one aggregate per call) and we want
// the gauge to tick during a scan.
export function createCoverageQuery() {
  return createQuery({
    queryKey: qk.coverage(),
    queryFn: getCoverageRollup,
    staleTime: 30_000,
    refetchInterval: 3_000
  });
}

// Cover-art providers ------------------------------------------------------

export type CoverProviderCapability = 'album_cover' | 'artist_thumb' | 'artist_fanart';

export type CoverProvider = {
  id: string;
  display_name: string;
  requires_api_key: boolean;
  supports: CoverProviderCapability[];
  enabled: boolean;
  api_key_set: boolean;
  display_order: number;
  testable: boolean;
};

export type CoverProvidersResponse = {
  providers: CoverProvider[];
  sources_version: number;
};

export async function getCoverProviders(): Promise<CoverProvidersResponse> {
  return api.get<CoverProvidersResponse>('/api/admin/cover-sources');
}

export type UpdateCoverProviderPatch = {
  enabled?: boolean;
  api_key?: string;
};

export type UpdateCoverProviderResponse = CoverProvider & {
  version_bumped: boolean;
};

export async function updateCoverProvider(
  id: string,
  patch: UpdateCoverProviderPatch
): Promise<UpdateCoverProviderResponse> {
  return api.patch<UpdateCoverProviderResponse>(`/api/admin/cover-sources/${id}`, patch);
}

export type TestCoverProviderResponse = {
  ok: boolean;
  duration_ms?: number;
  error?: string;
};

export async function testCoverProvider(id: string): Promise<TestCoverProviderResponse> {
  return api.post<TestCoverProviderResponse>(`/api/admin/cover-sources/${id}/test`, {});
}

// Re-search missing art --------------------------------------------------

export type ResearchMissingArtResponse = { version: number };

// Bumps cover_art_sources_meta.current_version. Every 'none' row becomes
// eligible for retry against the current provider chain on the next
// enrichment pass. Existing positively-sourced rows are not affected.
export async function researchMissingArt(): Promise<ResearchMissingArtResponse> {
  return api.post<ResearchMissingArtResponse>('/api/admin/cover-sources/research', {});
}

// Settings rarely change in operator time. 60s staleTime; refetches
// on window focus + after any mutation rather than polling.
export function createCoverProvidersQuery() {
  return createQuery({
    queryKey: qk.coverProviders(),
    queryFn: getCoverProviders,
    staleTime: 60_000
  });
}

// Tag-enrichment providers -------------------------------------------------
// Parallel to the cover-art providers surface, over /api/admin/tag-sources.
// Independent settings so a new folksonomy source is added without touching
// art config (#1490).

export type TagProviderCapability = 'track_tags';

export type TagProvider = {
  id: string;
  display_name: string;
  requires_api_key: boolean;
  supports: TagProviderCapability[];
  enabled: boolean;
  api_key_set: boolean;
  display_order: number;
  testable: boolean;
};

export type TagProvidersResponse = {
  providers: TagProvider[];
  sources_version: number;
};

export async function getTagProviders(): Promise<TagProvidersResponse> {
  return api.get<TagProvidersResponse>('/api/admin/tag-sources');
}

export type UpdateTagProviderPatch = {
  enabled?: boolean;
  api_key?: string;
};

export type UpdateTagProviderResponse = TagProvider & {
  version_bumped: boolean;
};

export async function updateTagProvider(
  id: string,
  patch: UpdateTagProviderPatch
): Promise<UpdateTagProviderResponse> {
  return api.patch<UpdateTagProviderResponse>(`/api/admin/tag-sources/${id}`, patch);
}

export type TestTagProviderResponse = {
  ok: boolean;
  duration_ms?: number;
  error?: string;
};

export async function testTagProvider(id: string): Promise<TestTagProviderResponse> {
  return api.post<TestTagProviderResponse>(`/api/admin/tag-sources/${id}/test`, {});
}

export function createTagProvidersQuery() {
  return createQuery({
    queryKey: qk.tagProviders(),
    queryFn: getTagProviders,
    staleTime: 60_000
  });
}

// Admin user-management ------------------------------------------------------

export type AdminUser = {
  id: string;
  username: string;
  display_name: string | null;
  is_admin: boolean;
  auto_approve_requests: boolean;
  debug_mode_enabled: boolean;
  created_at: string;
};

export type AdminInvite = {
  token: string;
  invited_by: string;
  note: string | null;
  created_at: string;
  expires_at: string;
  redeemed_at: string | null;
  redeemed_by: string | null;
};

export async function listUsers(): Promise<AdminUser[]> {
  const res = await api.get<{ users: AdminUser[] }>('/api/admin/users');
  return res.users;
}

export async function updateUserAdmin(id: string, isAdmin: boolean): Promise<AdminUser> {
  return api.put<AdminUser>(`/api/admin/users/${id}/admin`, { is_admin: isAdmin });
}

export async function listInvites(): Promise<AdminInvite[]> {
  const res = await api.get<{ invites: AdminInvite[] }>('/api/admin/invites');
  return res.invites;
}

export async function createInvite(note?: string): Promise<AdminInvite> {
  return api.post<AdminInvite>('/api/admin/invites', note ? { note } : {});
}

export async function deleteInvite(token: string): Promise<void> {
  await api.del(`/api/admin/invites/${token}`);
}

// U2 user-management actions -----------------------------------------------

export type CreateUserInput = {
  username: string;
  password: string;
  display_name?: string;
  is_admin?: boolean;
};

export async function createUser(input: CreateUserInput): Promise<AdminUser> {
  return api.post<AdminUser>('/api/admin/users', input);
}

export async function deleteUser(id: string): Promise<void> {
  await api.del(`/api/admin/users/${id}`);
}

export async function resetUserPassword(id: string, password: string): Promise<void> {
  await api.post(`/api/admin/users/${id}/reset-password`, { password });
}

export async function updateUserAutoApprove(id: string, autoApprove: boolean): Promise<AdminUser> {
  return api.put<AdminUser>(`/api/admin/users/${id}/auto-approve`, { auto_approve: autoApprove });
}

// Flip an account's diagnostics/debug-reporting opt-in (M9). Admin-set;
// the client obeys it (with a local per-device OFF switch).
export async function updateUserDebugMode(id: string, enabled: boolean): Promise<AdminUser> {
  return api.put<AdminUser>(`/api/admin/users/${id}/debug-mode`, { enabled });
}

export function createAdminUsersQuery() {
  return createQuery({
    queryKey: qk.adminUsers(),
    queryFn: listUsers
  });
}

export function createAdminInvitesQuery() {
  return createQuery({
    queryKey: qk.adminInvites(),
    queryFn: listInvites
  });
}

// SMTP config (U3) ----------------------------------------------------------

export type SMTPConfig = {
  enabled: boolean;
  host: string;
  port: number;
  username: string;
  password: string; // "***" when set, "" when unset
  from_address: string;
  from_name: string;
  use_tls: boolean;
};

export async function getSMTPConfig(): Promise<SMTPConfig> {
  return api.get<SMTPConfig>('/api/admin/smtp-config');
}

export async function updateSMTPConfig(input: SMTPConfig): Promise<void> {
  await api.put('/api/admin/smtp-config', input);
}

export async function testSMTPConfig(): Promise<void> {
  await api.post('/api/admin/smtp-config/test', {});
}

export function createSMTPConfigQuery() {
  return createQuery({
    queryKey: qk.smtpConfig(),
    queryFn: getSMTPConfig,
    staleTime: 60_000
  });
}

// Device diagnostics (M9) ---------------------------------------------------

// Coarse event category. The finer event sub-type lives inside `payload`.
export type DiagnosticKind =
  | 'connectivity'
  | 'upnp_sync'
  | 'power'
  | 'lifecycle'
  | 'heartbeat'
  | 'http';

export type AdminDiagnostic = {
  id: string;
  user_id: string;
  username: string;
  client_id: string;
  app_version?: string;
  os_version?: string;
  kind: DiagnosticKind | string;
  payload: Record<string, unknown>;
  occurred_at: string;
  received_at: string;
};

export type AdminDiagnosticDevice = {
  client_id: string;
  user_id: string;
  username: string;
  app_version: string;
  os_version: string;
  last_seen: string;
  event_count: number;
};

export type DiagnosticsFilter = {
  userId?: string;
  clientId?: string;
  kind?: string;
  from?: string; // RFC3339
  to?: string; // RFC3339
  limit?: number;
};

export async function listAdminDiagnostics(f: DiagnosticsFilter): Promise<AdminDiagnostic[]> {
  const params = new URLSearchParams();
  if (f.userId) params.set('user_id', f.userId);
  if (f.clientId) params.set('client_id', f.clientId);
  if (f.kind) params.set('kind', f.kind);
  if (f.from) params.set('from', f.from);
  if (f.to) params.set('to', f.to);
  if (f.limit !== undefined) params.set('limit', String(f.limit));
  const qs = params.toString();
  return api.get<AdminDiagnostic[]>(qs ? `/api/admin/diagnostics?${qs}` : '/api/admin/diagnostics');
}

export async function listDiagnosticDevices(userId?: string): Promise<AdminDiagnosticDevice[]> {
  const qs = userId ? `?user_id=${userId}` : '';
  return api.get<AdminDiagnosticDevice[]>(`/api/admin/diagnostics/devices${qs}`);
}

export function createAdminDiagnosticsQuery(f: DiagnosticsFilter) {
  return createQuery({
    queryKey: qk.adminDiagnostics(f as Record<string, string | number | undefined>),
    queryFn: () => listAdminDiagnostics(f),
    // The operator enables debug then watches events stream in; a short
    // poll keeps the timeline live without manual refresh.
    refetchInterval: 10_000,
    staleTime: 5_000
  });
}

export function createDiagnosticDevicesQuery(userId?: string) {
  return createQuery({
    queryKey: qk.adminDiagnosticDevices(userId),
    queryFn: () => listDiagnosticDevices(userId),
    staleTime: 15_000
  });
}

// Trusted-proxy depth (#2453) ---------------------------------------------

// detected_client_ip / forwarded_chain / remote_addr describe THIS request
// under the current setting, so the admin card can be verified rather than
// reasoned about: change the number, see what address you resolve to.
export type NetworkSettings = {
  trusted_proxy_hops: number;
  max_hops: number;
  detected_client_ip: string;
  forwarded_chain: string;
  remote_addr: string;
};

export async function getNetworkSettings(): Promise<NetworkSettings> {
  return api.get<NetworkSettings>('/api/admin/network-settings');
}

// Returns the payload recomputed under the new value, so the card can show
// the effect immediately instead of requiring a reload.
export async function updateNetworkSettings(hops: number): Promise<NetworkSettings> {
  return api.put<NetworkSettings>('/api/admin/network-settings', {
    trusted_proxy_hops: hops
  });
}
