import { SimpleLocalClient, SimpleLocalMapper, type IServiceCallResult } from '../../core/index.js';
import { OpenhardwaremonitorMapper } from './openhardwaremonitor.mapper.js';
import type { IOpenhardwaremonitorConfig, IOpenhardwaremonitorRawApiData, IOpenhardwaremonitorSnapshot } from './openhardwaremonitor.types.js';
import { openhardwaremonitorDefaultPort, openhardwaremonitorProfile } from './openhardwaremonitor.types.js';

export class OpenhardwaremonitorClient extends SimpleLocalClient<IOpenhardwaremonitorConfig> {
  private liveSnapshot?: IOpenhardwaremonitorSnapshot;

  constructor(private readonly configArg: IOpenhardwaremonitorConfig) {
    super(openhardwaremonitorProfile, configArg);
  }

  public override async getSnapshot(forceRefreshArg = false): Promise<IOpenhardwaremonitorSnapshot> {
    if (!forceRefreshArg && this.liveSnapshot) {
      return SimpleLocalMapper.clone(this.liveSnapshot);
    }

    if (!this.hasLiveHttpConfig()) {
      const snapshot = await super.getSnapshot(forceRefreshArg);
      this.liveSnapshot = snapshot;
      return SimpleLocalMapper.clone(this.liveSnapshot);
    }

    try {
      this.liveSnapshot = OpenhardwaremonitorMapper.toSnapshot({ config: this.configArg, rawData: await this.getRawData(), online: true, source: 'http' });
    } catch (errorArg) {
      this.liveSnapshot = OpenhardwaremonitorMapper.toSnapshot({ config: this.configArg, online: false, source: 'http', error: errorMessage(errorArg) });
    }
    return SimpleLocalMapper.clone(this.liveSnapshot);
  }

  public override async refresh(): Promise<IServiceCallResult> {
    this.liveSnapshot = undefined;
    if (!this.hasLiveHttpConfig()) {
      return super.refresh();
    }

    const snapshot = await this.getSnapshot(true);
    const success = snapshot.online && !snapshot.error;
    return { success, error: success ? undefined : snapshot.error, data: snapshot };
  }

  public async getRawData(): Promise<IOpenhardwaremonitorRawApiData> {
    return {
      data: await this.requestData(),
      fetchedAt: new Date().toISOString(),
    };
  }

  private async requestData(): Promise<Record<string, unknown>> {
    const endpoint = this.endpoint();
    if (!endpoint) {
      throw new Error('Open Hardware Monitor local HTTP requires config.host or config.url.');
    }

    const headers: Record<string, string> = { accept: 'application/json' };
    if (this.configArg.username !== undefined && this.configArg.password !== undefined) {
      headers.authorization = `Basic ${Buffer.from(`${this.configArg.username}:${this.configArg.password}`).toString('base64')}`;
    }

    const response = await globalThis.fetch(endpoint.dataUrl, {
      headers,
      signal: AbortSignal.timeout(this.configArg.timeoutMs || 5000),
    });
    const text = await response.text();
    if (response.status === 401) {
      throw new Error('Open Hardware Monitor authentication failed with HTTP 401.');
    }
    if (!response.ok) {
      throw new Error(`Open Hardware Monitor /data.json failed with HTTP ${response.status}: ${text.slice(0, 160)}`);
    }
    try {
      const parsed = JSON.parse(text) as unknown;
      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error('response was not a JSON object');
      }
      return parsed as Record<string, unknown>;
    } catch (errorArg) {
      throw new Error(`Open Hardware Monitor /data.json returned invalid JSON: ${errorMessage(errorArg)}`);
    }
  }

  private hasLiveHttpConfig(): boolean {
    return Boolean(this.endpoint()) && !this.configArg.client && !this.configArg.snapshotProvider;
  }

  private endpoint(): IOpenhardwaremonitorEndpoint | undefined {
    const rawUrl = stringValue(this.configArg.url) || stringValue(this.configArg.metadata?.url);
    const parsedUrl = rawUrl ? parseUrl(rawUrl) : undefined;
    const parsedHost = !parsedUrl && this.configArg.host ? parseUrl(this.configArg.host) : undefined;
    const url = parsedUrl || parsedHost;
    if (url) {
      const protocol = url.protocol === 'https:' ? 'https' : 'http';
      const port = this.configArg.port || numberValue(url.port) || (protocol === 'https' ? 443 : openhardwaremonitorDefaultPort);
      const origin = `${protocol}://${url.hostname}${portPart(protocol, port)}`;
      const dataPath = url.pathname && url.pathname !== '/' ? url.pathname : '/data.json';
      return { host: url.hostname, port, protocol, origin, dataUrl: `${origin}${dataPath}` };
    }

    const host = stringValue(this.configArg.host);
    if (!host) {
      return undefined;
    }
    const protocol = this.configArg.useTls ? 'https' : 'http';
    const port = this.configArg.port || (protocol === 'https' ? 443 : openhardwaremonitorDefaultPort);
    const origin = `${protocol}://${host}${portPart(protocol, port)}`;
    return { host, port, protocol, origin, dataUrl: `${origin}/data.json` };
  }
}

interface IOpenhardwaremonitorEndpoint {
  host: string;
  port: number;
  protocol: 'http' | 'https';
  origin: string;
  dataUrl: string;
}

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
const numberValue = (valueArg: unknown): number | undefined => typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : stringValue(valueArg) && Number.isFinite(Number(valueArg)) ? Number(valueArg) : undefined;
const parseUrl = (valueArg: string): URL | undefined => {
  if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
    return undefined;
  }
  try {
    return new URL(valueArg);
  } catch {
    return undefined;
  }
};
const portPart = (protocolArg: 'http' | 'https', portArg: number): string => (protocolArg === 'http' && portArg === 80) || (protocolArg === 'https' && portArg === 443) ? '' : `:${portArg}`;
const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);
