import type {
  IAdguardBooleanStatus,
  IAdguardClientCommand,
  IAdguardCommandResult,
  IAdguardConfig,
  IAdguardFilteringStatus,
  IAdguardQueryLogConfig,
  IAdguardSafeSearchConfig,
  IAdguardServerStatus,
  IAdguardSnapshot,
  IAdguardStats,
  IAdguardVersionInfo,
  TAdguardJsonValue,
  TAdguardSnapshotSource,
} from './adguard.types.js';
import { adguardDefaultPort, adguardDefaultTimeoutMs } from './adguard.types.js';

export class AdguardClient {
  constructor(private readonly config: IAdguardConfig) {}

  public async getSnapshot(): Promise<IAdguardSnapshot> {
    if (this.config.snapshot) {
      return this.normalizeSnapshot(this.cloneSnapshot(this.config.snapshot), 'snapshot');
    }

    if (this.config.host) {
      try {
        return this.normalizeSnapshot(await this.fetchSnapshot(), 'http');
      } catch (errorArg) {
        return this.normalizeSnapshot(this.snapshotFromConfig(false, errorArg instanceof Error ? errorArg.message : String(errorArg)), 'runtime');
      }
    }

    return this.normalizeSnapshot(this.snapshotFromConfig(false), 'manual');
  }

  public async refresh(): Promise<IAdguardSnapshot> {
    return this.getSnapshot();
  }

  public async ping(): Promise<boolean> {
    if (!this.config.host) {
      return Boolean(this.config.snapshot);
    }
    const status = await this.requestJson<IAdguardServerStatus>('/status');
    return status.running !== false;
  }

  public async sendCommand(commandArg: IAdguardClientCommand): Promise<IAdguardCommandResult> {
    if (this.config.commandExecutor) {
      return this.commandResult(await this.config.commandExecutor(commandArg), commandArg);
    }

    if (!this.config.host) {
      return {
        success: false,
        error: 'AdGuard Home live commands require config.host or commandExecutor; snapshot-only commands are not reported as successful.',
        data: { command: commandArg },
      };
    }

    const data = await this.requestJson<TAdguardJsonValue>(commandArg.path, {
      method: commandArg.method,
      body: commandArg.payload,
    });
    return { success: true, data: { command: commandArg, response: data } };
  }

  public async destroy(): Promise<void> {}

  private async fetchSnapshot(): Promise<IAdguardSnapshot> {
    const status = await this.requestJson<IAdguardServerStatus>('/status');
    const [filtering, queryLog, safebrowsing, safesearch, parental, stats, update] = await Promise.all([
      this.requestJson<IAdguardFilteringStatus>('/filtering/status').catch(() => ({})),
      this.requestJson<IAdguardQueryLogConfig>('/querylog/config').catch(() => this.requestJson<IAdguardQueryLogConfig>('/querylog_info').catch(() => ({}))),
      this.requestJson<IAdguardBooleanStatus>('/safebrowsing/status').catch(() => ({})),
      this.requestJson<IAdguardSafeSearchConfig>('/safesearch/status').catch(() => ({})),
      this.requestJson<IAdguardBooleanStatus>('/parental/status').catch(() => ({})),
      this.requestJson<IAdguardStats>('/stats').catch(() => ({})),
      this.requestJson<IAdguardVersionInfo>('/version.json', { method: 'POST', body: { recheck_now: false } }).catch(() => undefined),
    ]);

    return {
      online: status.running !== false,
      status,
      filtering,
      queryLog,
      safebrowsing,
      safesearch,
      parental,
      stats,
      update,
      host: this.config.host,
      port: this.config.port || status.http_port || adguardDefaultPort,
      ssl: this.config.ssl ?? false,
      basePath: this.config.basePath,
      name: this.config.name,
      uniqueId: this.config.uniqueId,
      updatedAt: new Date().toISOString(),
      source: 'http',
    };
  }

  private async requestJson<TResponse>(pathArg: string, optionsArg: { method?: 'GET' | 'POST' | 'PUT'; body?: unknown } = {}): Promise<TResponse> {
    const method = optionsArg.method || 'GET';
    const headers: Record<string, string> = {};
    let body: string | undefined;
    if (optionsArg.body !== undefined) {
      headers['content-type'] = 'application/json';
      body = JSON.stringify(optionsArg.body);
    }
    if (this.config.username || this.config.password) {
      headers.authorization = `Basic ${Buffer.from(`${this.config.username || ''}:${this.config.password || ''}`).toString('base64')}`;
    }

    const response = await globalThis.fetch(`${this.controlUrl()}${pathArg}`, {
      method,
      headers,
      body,
      signal: AbortSignal.timeout(this.config.timeoutMs || adguardDefaultTimeoutMs),
    });
    const text = await response.text();
    if (!response.ok) {
      throw new Error(`AdGuard Home request ${pathArg} failed with HTTP ${response.status}: ${text}`);
    }
    if (!text) {
      return {} as TResponse;
    }
    return JSON.parse(text) as TResponse;
  }

  private controlUrl(): string {
    if (!this.config.host) {
      throw new Error('AdGuard Home host is required for HTTP API access.');
    }
    const protocol = this.config.ssl ? 'https' : 'http';
    const port = this.config.port || adguardDefaultPort;
    const defaultPort = protocol === 'https' ? 443 : 80;
    const portPart = port === defaultPort ? '' : `:${port}`;
    const basePath = this.normalizeBasePath(this.config.basePath);
    return `${protocol}://${this.config.host}${portPart}${basePath}/control`;
  }

  private normalizeBasePath(valueArg: string | undefined): string {
    if (!valueArg) {
      return '';
    }
    const trimmed = valueArg.trim().replace(/\/+$/g, '');
    if (!trimmed || trimmed === '/') {
      return '';
    }
    return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
  }

  private snapshotFromConfig(onlineArg: boolean, errorArg?: string): IAdguardSnapshot {
    return {
      online: onlineArg,
      status: {
        running: onlineArg,
        version: typeof this.config.metadata?.version === 'string' ? this.config.metadata.version : undefined,
      },
      filtering: {},
      queryLog: {},
      safebrowsing: {},
      safesearch: {},
      parental: {},
      stats: {},
      host: this.config.host,
      port: this.config.port || (this.config.host ? adguardDefaultPort : undefined),
      ssl: this.config.ssl ?? false,
      basePath: this.config.basePath,
      name: this.config.name,
      uniqueId: this.config.uniqueId,
      updatedAt: new Date().toISOString(),
      source: 'runtime',
      error: errorArg,
    };
  }

  private normalizeSnapshot(snapshotArg: IAdguardSnapshot, sourceArg: TAdguardSnapshotSource): IAdguardSnapshot {
    return {
      ...snapshotArg,
      online: snapshotArg.online,
      status: snapshotArg.status || {},
      filtering: snapshotArg.filtering || {},
      queryLog: snapshotArg.queryLog || {},
      safebrowsing: snapshotArg.safebrowsing || {},
      safesearch: snapshotArg.safesearch || {},
      parental: snapshotArg.parental || {},
      stats: snapshotArg.stats || {},
      host: snapshotArg.host || this.config.host,
      port: snapshotArg.port || this.config.port || (snapshotArg.host || this.config.host ? adguardDefaultPort : undefined),
      ssl: snapshotArg.ssl ?? this.config.ssl ?? false,
      basePath: snapshotArg.basePath ?? this.config.basePath,
      name: snapshotArg.name || this.config.name,
      uniqueId: snapshotArg.uniqueId || this.config.uniqueId,
      updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
      source: snapshotArg.source || sourceArg,
    };
  }

  private cloneSnapshot(snapshotArg: IAdguardSnapshot): IAdguardSnapshot {
    return JSON.parse(JSON.stringify(snapshotArg)) as IAdguardSnapshot;
  }

  private commandResult(resultArg: unknown, commandArg: IAdguardClientCommand): IAdguardCommandResult {
    if (this.isCommandResult(resultArg)) {
      return resultArg;
    }
    return { success: true, data: resultArg ?? { command: commandArg } };
  }

  private isCommandResult(valueArg: unknown): valueArg is IAdguardCommandResult {
    return typeof valueArg === 'object' && valueArg !== null && 'success' in valueArg;
  }
}
