import { SimpleLocalClient } from '../../core/index.js';
import { LidarrMapper } from './lidarr.mapper.js';
import type { ILidarrAlbum, ILidarrConfig, ILidarrLiveRawData, ILidarrPagedRecords, ILidarrQueueItem, ILidarrRootFolder, ILidarrSystemStatus } from './lidarr.types.js';
import { lidarrDefaultMaxRecords, lidarrDefaultPort, lidarrProfile } from './lidarr.types.js';

const defaultTimeoutMs = 5000;

export class LidarrClient extends SimpleLocalClient<ILidarrConfig> {
  constructor(private readonly lidarrConfig: ILidarrConfig) {
    super(lidarrProfile, lidarrConfig);
  }

  public override async getSnapshot(forceRefreshArg = false) {
    if (this.lidarrConfig.client || this.lidarrConfig.snapshotProvider || (!forceRefreshArg && this.lidarrConfig.snapshot)) {
      return super.getSnapshot(forceRefreshArg);
    }
    if ((this.lidarrConfig.host || this.lidarrConfig.url) && !this.apiKey()) {
      return LidarrMapper.toSnapshot({
        config: this.lidarrConfig,
        rawData: { fetchedAt: new Date().toISOString() },
        online: false,
        source: 'http',
        error: 'Lidarr live HTTP API polling requires config.apiKey or config.token.',
      });
    }
    if (!this.hasLiveTarget()) {
      return super.getSnapshot(forceRefreshArg);
    }
    try {
      const rawData = await this.getRawData();
      return LidarrMapper.toSnapshot({ config: this.lidarrConfig, rawData, online: true, source: 'http' });
    } catch (errorArg) {
      return LidarrMapper.toSnapshot({
        config: this.lidarrConfig,
        rawData: { fetchedAt: new Date().toISOString() },
        online: false,
        source: 'http',
        error: errorArg instanceof Error ? errorArg.message : String(errorArg),
      });
    }
  }

  public async getRawData(): Promise<ILidarrLiveRawData> {
    if (!this.hasLiveTarget()) {
      const snapshot = await super.getSnapshot(true);
      return { fetchedAt: snapshot.updatedAt };
    }
    const pageSize = this.lidarrConfig.maxRecords || lidarrDefaultMaxRecords;
    const [systemStatus, rootFolders, queue, wanted, albums] = await Promise.all([
      this.getSystemStatus(),
      this.getRootFolders(),
      this.getQueue(pageSize),
      this.getWanted(pageSize),
      this.getAlbums(),
    ]);
    return {
      systemStatus,
      rootFolders,
      queue,
      wanted,
      albums,
      fetchedAt: new Date().toISOString(),
    };
  }

  public async getSystemStatus(): Promise<ILidarrSystemStatus> {
    return this.fetchJson<ILidarrSystemStatus>('/api/v1/system/status');
  }

  public async getRootFolders(): Promise<ILidarrRootFolder[]> {
    return this.fetchJson<ILidarrRootFolder[]>('/api/v1/rootfolder');
  }

  public async getQueue(pageSizeArg = lidarrDefaultMaxRecords): Promise<ILidarrPagedRecords<ILidarrQueueItem>> {
    return this.fetchJson<ILidarrPagedRecords<ILidarrQueueItem>>('/api/v1/queue', { pageSize: pageSizeArg });
  }

  public async getWanted(pageSizeArg = lidarrDefaultMaxRecords): Promise<ILidarrPagedRecords<ILidarrAlbum>> {
    return this.fetchJson<ILidarrPagedRecords<ILidarrAlbum>>('/api/v1/wanted/missing', { pageSize: pageSizeArg });
  }

  public async getAlbums(): Promise<ILidarrAlbum[]> {
    return this.fetchJson<ILidarrAlbum[]>('/api/v1/album');
  }

  private async fetchJson<TValue>(pathArg: string, queryArg?: Record<string, string | number | boolean | undefined>): Promise<TValue> {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), this.lidarrConfig.timeoutMs || defaultTimeoutMs);
    try {
      const response = await globalThis.fetch(this.requestUrl(pathArg, queryArg), {
        method: 'GET',
        headers: this.headers(),
        signal: controller.signal,
      });
      const text = await response.text();
      if (!response.ok) {
        throw new Error(`Lidarr HTTP API ${pathArg} failed with HTTP ${response.status}: ${text.slice(0, 160)}`);
      }
      return text ? JSON.parse(text) as TValue : undefined as TValue;
    } finally {
      clearTimeout(timeout);
    }
  }

  private requestUrl(pathArg: string, queryArg?: Record<string, string | number | boolean | undefined>): string {
    const url = new URL(pathArg, `${this.baseUrl()}/`);
    for (const [key, value] of Object.entries(queryArg || {})) {
      if (value !== undefined) {
        url.searchParams.set(key, String(value));
      }
    }
    return url.toString();
  }

  private headers(): Record<string, string> {
    const apiKey = this.apiKey();
    if (!apiKey) {
      throw new Error('Lidarr live HTTP API polling requires config.apiKey or config.token.');
    }
    return {
      accept: 'application/json',
      'x-api-key': apiKey,
    };
  }

  private baseUrl(): string {
    if (this.lidarrConfig.url) {
      return this.lidarrConfig.url.replace(/\/+$/, '');
    }
    if (!this.lidarrConfig.host) {
      throw new Error('Lidarr host or url is required for live HTTP API polling.');
    }
    const protocol = this.lidarrConfig.useTls ? 'https' : 'http';
    const port = this.lidarrConfig.port || lidarrDefaultPort;
    const path = typeof this.lidarrConfig.path === 'string' && this.lidarrConfig.path.trim() ? `/${this.lidarrConfig.path.replace(/^\/+|\/+$/g, '')}` : '';
    return `${protocol}://${this.lidarrConfig.host}:${port}${path}`;
  }

  private hasLiveTarget(): boolean {
    return Boolean((this.lidarrConfig.host || this.lidarrConfig.url) && this.apiKey());
  }

  private apiKey(): string | undefined {
    return this.lidarrConfig.apiKey || this.lidarrConfig.token;
  }
}
