/**
 * Synor Mining SDK Client
 * Pool connections, block templates, hashrate stats, and GPU management.
 */

import {
  MiningConfig,
  PoolConfig,
  StratumConnection,
  BlockTemplate,
  MinedWork,
  SubmitResult,
  Hashrate,
  MiningStats,
  TimePeriod,
  Earnings,
  MiningDevice,
  DeviceConfig,
  WorkerInfo,
  PoolStats,
  MiningAlgorithm,
  DevicesResponse,
  WorkersResponse,
  AlgorithmsResponse,
} from './types';

const DEFAULT_ENDPOINT = 'https://mining.synor.io/v1';
const DEFAULT_TIMEOUT = 30000;
const DEFAULT_RETRIES = 3;

export class SynorMining {
  private config: Required<MiningConfig>;
  private closed = false;
  private activeConnection?: StratumConnection;

  constructor(config: MiningConfig) {
    this.config = {
      apiKey: config.apiKey,
      endpoint: config.endpoint ?? DEFAULT_ENDPOINT,
      timeout: config.timeout ?? DEFAULT_TIMEOUT,
      retries: config.retries ?? DEFAULT_RETRIES,
      debug: config.debug ?? false,
    };
  }

  // ==================== Pool Connection ====================

  /**
   * Connect to a mining pool.
   */
  async connect(pool: PoolConfig): Promise<StratumConnection> {
    const connection = await this.post<StratumConnection>('/pool/connect', pool);
    this.activeConnection = connection;
    return connection;
  }

  /**
   * Disconnect from the current pool.
   */
  async disconnect(): Promise<void> {
    if (this.activeConnection) {
      await this.post<void>(`/pool/disconnect/${this.activeConnection.id}`, {});
      this.activeConnection = undefined;
    }
  }

  /**
   * Get current connection status.
   */
  async getConnectionStatus(): Promise<StratumConnection | null> {
    if (!this.activeConnection) return null;
    try {
      return await this.get<StratumConnection>(`/pool/status/${this.activeConnection.id}`);
    } catch {
      return null;
    }
  }

  /**
   * Reconnect to the pool.
   */
  async reconnect(): Promise<StratumConnection> {
    if (!this.activeConnection) {
      throw new MiningError('No active connection to reconnect');
    }
    return await this.post<StratumConnection>(`/pool/reconnect/${this.activeConnection.id}`, {});
  }

  // ==================== Mining Operations ====================

  /**
   * Get the current block template.
   */
  async getBlockTemplate(): Promise<BlockTemplate> {
    return await this.get<BlockTemplate>('/mining/template');
  }

  /**
   * Submit mined work.
   */
  async submitWork(work: MinedWork): Promise<SubmitResult> {
    return await this.post<SubmitResult>('/mining/submit', work);
  }

  /**
   * Get work from pool (stratum getwork).
   */
  async getWork(): Promise<{ work: string; target: string; algorithm: string }> {
    return await this.get<{ work: string; target: string; algorithm: string }>('/mining/getwork');
  }

  /**
   * Start mining on all enabled devices.
   */
  async startMining(algorithm?: string): Promise<{ started: boolean; devices: string[] }> {
    const body = algorithm ? { algorithm } : {};
    return await this.post<{ started: boolean; devices: string[] }>('/mining/start', body);
  }

  /**
   * Stop mining on all devices.
   */
  async stopMining(): Promise<{ stopped: boolean }> {
    return await this.post<{ stopped: boolean }>('/mining/stop', {});
  }

  // ==================== Stats ====================

  /**
   * Get current hashrate.
   */
  async getHashrate(): Promise<Hashrate> {
    return await this.get<Hashrate>('/stats/hashrate');
  }

  /**
   * Get mining stats.
   */
  async getStats(): Promise<MiningStats> {
    return await this.get<MiningStats>('/stats');
  }

  /**
   * Get earnings for a time period.
   */
  async getEarnings(period?: TimePeriod): Promise<Earnings> {
    const query = period ? `?period=${period}` : '';
    return await this.get<Earnings>(`/stats/earnings${query}`);
  }

  /**
   * Get earnings history.
   */
  async getEarningsHistory(limit?: number, offset?: number): Promise<Earnings[]> {
    const params = new URLSearchParams();
    if (limit) params.set('limit', limit.toString());
    if (offset) params.set('offset', offset.toString());
    const query = params.toString() ? `?${params.toString()}` : '';
    const response = await this.get<{ earnings: Earnings[] }>(`/stats/earnings/history${query}`);
    return response.earnings ?? [];
  }

  /**
   * Get pool stats.
   */
  async getPoolStats(): Promise<PoolStats> {
    return await this.get<PoolStats>('/pool/stats');
  }

  // ==================== GPU Management ====================

  /**
   * List all mining devices.
   */
  async listDevices(): Promise<MiningDevice[]> {
    const response = await this.get<DevicesResponse>('/devices');
    return response.devices ?? [];
  }

  /**
   * Get device details.
   */
  async getDevice(deviceId: string): Promise<MiningDevice> {
    return await this.get<MiningDevice>(`/devices/${encodeURIComponent(deviceId)}`);
  }

  /**
   * Set device configuration.
   */
  async setDeviceConfig(deviceId: string, config: DeviceConfig): Promise<MiningDevice> {
    return await this.post<MiningDevice>(
      `/devices/${encodeURIComponent(deviceId)}/config`,
      config
    );
  }

  /**
   * Enable a device for mining.
   */
  async enableDevice(deviceId: string): Promise<MiningDevice> {
    return await this.post<MiningDevice>(`/devices/${encodeURIComponent(deviceId)}/enable`, {});
  }

  /**
   * Disable a device.
   */
  async disableDevice(deviceId: string): Promise<MiningDevice> {
    return await this.post<MiningDevice>(`/devices/${encodeURIComponent(deviceId)}/disable`, {});
  }

  /**
   * Reset device to default settings.
   */
  async resetDevice(deviceId: string): Promise<MiningDevice> {
    return await this.post<MiningDevice>(`/devices/${encodeURIComponent(deviceId)}/reset`, {});
  }

  // ==================== Workers ====================

  /**
   * List all workers.
   */
  async listWorkers(): Promise<WorkerInfo[]> {
    const response = await this.get<WorkersResponse>('/workers');
    return response.workers ?? [];
  }

  /**
   * Get worker details.
   */
  async getWorker(workerId: string): Promise<WorkerInfo> {
    return await this.get<WorkerInfo>(`/workers/${encodeURIComponent(workerId)}`);
  }

  /**
   * Create a new worker.
   */
  async createWorker(name: string): Promise<WorkerInfo> {
    return await this.post<WorkerInfo>('/workers', { name });
  }

  /**
   * Delete a worker.
   */
  async deleteWorker(workerId: string): Promise<void> {
    await this.delete(`/workers/${encodeURIComponent(workerId)}`);
  }

  // ==================== Algorithms ====================

  /**
   * Get supported mining algorithms.
   */
  async getSupportedAlgorithms(): Promise<MiningAlgorithm[]> {
    const response = await this.get<AlgorithmsResponse>('/algorithms');
    return response.algorithms ?? [];
  }

  /**
   * Get current algorithm.
   */
  async getCurrentAlgorithm(): Promise<MiningAlgorithm> {
    return await this.get<MiningAlgorithm>('/algorithms/current');
  }

  /**
   * Switch to a different algorithm.
   */
  async switchAlgorithm(algorithm: string): Promise<{ switched: boolean }> {
    return await this.post<{ switched: boolean }>('/algorithms/switch', { algorithm });
  }

  // ==================== Lifecycle ====================

  /**
   * Health check.
   */
  async healthCheck(): Promise<boolean> {
    try {
      const response = await this.get<{ status: string }>('/health');
      return response.status === 'healthy';
    } catch {
      return false;
    }
  }

  /**
   * Check if client is closed.
   */
  get isClosed(): boolean {
    return this.closed;
  }

  /**
   * Close the client.
   */
  async close(): Promise<void> {
    if (this.activeConnection) {
      await this.disconnect();
    }
    this.closed = true;
  }

  // ==================== Private HTTP Methods ====================

  private async get<T>(path: string): Promise<T> {
    return await this.execute(async () => {
      const response = await fetch(`${this.config.endpoint}${path}`, {
        method: 'GET',
        headers: this.headers(),
        signal: AbortSignal.timeout(this.config.timeout),
      });
      await this.ensureSuccess(response);
      return await response.json();
    });
  }

  private async post<T>(path: string, body: unknown): Promise<T> {
    return await this.execute(async () => {
      const response = await fetch(`${this.config.endpoint}${path}`, {
        method: 'POST',
        headers: this.headers(),
        body: JSON.stringify(body),
        signal: AbortSignal.timeout(this.config.timeout),
      });
      await this.ensureSuccess(response);
      const text = await response.text();
      return text ? JSON.parse(text) : undefined;
    });
  }

  private async delete(path: string): Promise<void> {
    return await this.execute(async () => {
      const response = await fetch(`${this.config.endpoint}${path}`, {
        method: 'DELETE',
        headers: this.headers(),
        signal: AbortSignal.timeout(this.config.timeout),
      });
      await this.ensureSuccess(response);
    });
  }

  private headers(): Record<string, string> {
    return {
      'Authorization': `Bearer ${this.config.apiKey}`,
      'Content-Type': 'application/json',
      'X-SDK-Version': 'js/0.1.0',
    };
  }

  private async execute<T>(operation: () => Promise<T>): Promise<T> {
    if (this.closed) {
      throw new MiningError('Client has been closed');
    }

    let lastError: Error | undefined;
    for (let attempt = 0; attempt < this.config.retries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error as Error;
        if (this.config.debug) {
          console.log(`Attempt ${attempt + 1} failed:`, error);
        }
        if (attempt < this.config.retries - 1) {
          await this.sleep(1000 * Math.pow(2, attempt));
        }
      }
    }
    throw lastError!;
  }

  private async ensureSuccess(response: Response): Promise<void> {
    if (!response.ok) {
      const body = await response.text();
      let message = `HTTP ${response.status}`;
      let code: string | undefined;
      try {
        const error = JSON.parse(body);
        message = error.message ?? message;
        code = error.code;
      } catch {
        // ignore parse errors
      }
      throw new MiningError(message, code, response.status);
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

/**
 * Mining SDK Error
 */
export class MiningError extends Error {
  constructor(
    message: string,
    public readonly code?: string,
    public readonly statusCode?: number
  ) {
    super(message);
    this.name = 'MiningError';
  }
}

// Re-export types
export * from './types';
