# frozen_string_literal: true

require "faraday"
require "json"
require "uri"

module SynorMining
  # Synor Mining SDK client for Ruby.
  # Pool connections, block templates, hashrate stats, and GPU management.
  class Client
    attr_reader :closed, :active_connection

    def initialize(config)
      @config = config
      @closed = false
      @active_connection = nil
      @conn = Faraday.new(url: config.endpoint) do |f|
        f.request :json
        f.response :json
        f.options.timeout = config.timeout
        f.headers["Authorization"] = "Bearer #{config.api_key}"
        f.headers["Content-Type"] = "application/json"
        f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
      end
    end

    # ==================== Pool Operations ====================

    def connect(pool:)
      body = { url: pool.url, user: pool.user }
      body[:password] = pool.password if pool.password
      body[:algorithm] = pool.algorithm if pool.algorithm
      body[:difficulty] = pool.difficulty if pool.difficulty
      response = post("/pool/connect", body)
      @active_connection = parse_stratum_connection(response)
    end

    def disconnect
      return unless @active_connection

      post("/pool/disconnect", {})
      @active_connection = nil
    end

    def get_connection
      response = get("/pool/connection")
      @active_connection = parse_stratum_connection(response)
    end

    def get_pool_stats
      response = get("/pool/stats")
      parse_pool_stats(response)
    end

    def connected?
      @active_connection&.status == ConnectionStatus::CONNECTED
    end

    # ==================== Mining Operations ====================

    def get_block_template
      response = get("/mining/template")
      parse_block_template(response)
    end

    def submit_work(work:)
      body = {
        template_id: work.template_id,
        nonce: work.nonce,
        extra_nonce: work.extra_nonce,
        timestamp: work.timestamp,
        hash: work.hash
      }
      response = post("/mining/submit", body)
      parse_submit_result(response)
    end

    def start_mining(algorithm: nil)
      body = algorithm ? { algorithm: algorithm } : {}
      post("/mining/start", body)
    end

    def stop_mining
      post("/mining/stop", {})
    end

    def mining?
      response = get("/mining/status")
      response["mining"] == true
    end

    # ==================== Stats Operations ====================

    def get_hashrate
      response = get("/stats/hashrate")
      parse_hashrate(response)
    end

    def get_stats
      response = get("/stats")
      parse_mining_stats(response)
    end

    def get_earnings(period: nil)
      path = period ? "/stats/earnings?period=#{period}" : "/stats/earnings"
      response = get(path)
      parse_earnings(response)
    end

    def get_share_stats
      response = get("/stats/shares")
      parse_share_stats(response)
    end

    # ==================== Device Operations ====================

    def list_devices
      response = get("/devices")
      (response["devices"] || []).map { |d| parse_mining_device(d) }
    end

    def get_device(device_id)
      response = get("/devices/#{encode(device_id)}")
      parse_mining_device(response)
    end

    def set_device_config(device_id:, config:)
      body = { enabled: config.enabled }
      body[:intensity] = config.intensity if config.intensity
      body[:power_limit] = config.power_limit if config.power_limit
      body[:core_clock_offset] = config.core_clock_offset if config.core_clock_offset
      body[:memory_clock_offset] = config.memory_clock_offset if config.memory_clock_offset
      body[:fan_speed] = config.fan_speed if config.fan_speed
      response = put("/devices/#{encode(device_id)}/config", body)
      parse_mining_device(response)
    end

    def enable_device(device_id)
      set_device_config(device_id: device_id, config: DeviceConfig.new(enabled: true))
    end

    def disable_device(device_id)
      set_device_config(device_id: device_id, config: DeviceConfig.new(enabled: false))
    end

    # ==================== Worker Operations ====================

    def list_workers
      response = get("/workers")
      (response["workers"] || []).map { |w| parse_worker_info(w) }
    end

    def get_worker(worker_id)
      response = get("/workers/#{encode(worker_id)}")
      parse_worker_info(response)
    end

    def remove_worker(worker_id)
      delete("/workers/#{encode(worker_id)}")
    end

    # ==================== Algorithm Operations ====================

    def list_algorithms
      response = get("/algorithms")
      (response["algorithms"] || []).map { |a| parse_mining_algorithm(a) }
    end

    def get_algorithm(name)
      response = get("/algorithms/#{encode(name)}")
      parse_mining_algorithm(response)
    end

    def switch_algorithm(algorithm)
      post("/algorithms/switch", { algorithm: algorithm })
    end

    def get_most_profitable
      response = get("/algorithms/profitable")
      parse_mining_algorithm(response)
    end

    # ==================== Lifecycle ====================

    def health_check
      response = get("/health")
      response["status"] == "healthy"
    rescue StandardError
      false
    end

    def close
      @closed = true
      @active_connection = nil
      @conn.close if @conn.respond_to?(:close)
    end

    private

    def get(path, params = {})
      execute { @conn.get(path, params).body }
    end

    def post(path, body)
      execute { @conn.post(path, body).body }
    end

    def put(path, body)
      execute { @conn.put(path, body).body }
    end

    def delete(path)
      execute { @conn.delete(path).body }
    end

    def execute
      raise ClientClosedError, "Client has been closed" if @closed

      last_error = nil
      @config.retries.times do |attempt|
        begin
          response = yield
          check_error(response) if response.is_a?(Hash)
          return response
        rescue StandardError => e
          last_error = e
          sleep(2**attempt) if attempt < @config.retries - 1
        end
      end
      raise last_error
    end

    def check_error(response)
      return unless response["error"] || (response["code"] && response["message"])

      message = response["message"] || response["error"] || "Unknown error"
      code = response["code"]
      status = response["status_code"] || 0
      raise HttpError.new(message, status_code: status, code: code)
    end

    def encode(str)
      URI.encode_www_form_component(str)
    end

    # Parsing methods

    def parse_stratum_connection(data)
      StratumConnection.new(
        id: data["id"],
        pool: data["pool"],
        status: data["status"],
        algorithm: data["algorithm"],
        difficulty: data["difficulty"],
        connected_at: data["connected_at"],
        accepted_shares: data["accepted_shares"],
        rejected_shares: data["rejected_shares"],
        stale_shares: data["stale_shares"],
        last_share_at: data["last_share_at"]
      )
    end

    def parse_pool_stats(data)
      PoolStats.new(
        url: data["url"],
        workers: data["workers"],
        hashrate: data["hashrate"],
        difficulty: data["difficulty"],
        last_block: data["last_block"],
        blocks_found_24h: data["blocks_found_24h"],
        luck: data["luck"]
      )
    end

    def parse_block_template(data)
      BlockTemplate.new(
        id: data["id"],
        previous_block_hash: data["previous_block_hash"],
        merkle_root: data["merkle_root"],
        timestamp: data["timestamp"],
        bits: data["bits"],
        height: data["height"],
        coinbase_value: data["coinbase_value"],
        transactions: (data["transactions"] || []).map { |t| parse_template_transaction(t) },
        target: data["target"],
        algorithm: data["algorithm"],
        extra_nonce: data["extra_nonce"]
      )
    end

    def parse_template_transaction(data)
      TemplateTransaction.new(
        txid: data["txid"],
        data: data["data"],
        fee: data["fee"],
        weight: data["weight"]
      )
    end

    def parse_submit_result(data)
      SubmitResult.new(
        status: data["status"],
        share: data["share"] ? parse_share_info(data["share"]) : nil,
        block_found: data["block_found"],
        reason: data["reason"],
        block_hash: data["block_hash"],
        reward: data["reward"]
      )
    end

    def parse_share_info(data)
      ShareInfo.new(
        hash: data["hash"],
        difficulty: data["difficulty"],
        timestamp: data["timestamp"],
        accepted: data["accepted"]
      )
    end

    def parse_hashrate(data)
      Hashrate.new(
        current: data["current"],
        average_1h: data["average_1h"],
        average_24h: data["average_24h"],
        peak: data["peak"],
        unit: data["unit"]
      )
    end

    def parse_share_stats(data)
      ShareStats.new(
        accepted: data["accepted"],
        rejected: data["rejected"],
        stale: data["stale"],
        total: data["total"],
        accept_rate: data["accept_rate"]
      )
    end

    def parse_mining_stats(data)
      MiningStats.new(
        hashrate: data["hashrate"] ? parse_hashrate(data["hashrate"]) : nil,
        shares: data["shares"] ? parse_share_stats(data["shares"]) : nil,
        uptime: data["uptime"],
        efficiency: data["efficiency"],
        earnings: data["earnings"] ? parse_earnings_snapshot(data["earnings"]) : nil,
        power_consumption: data["power_consumption"],
        temperature: data["temperature"] ? parse_device_temperature(data["temperature"]) : nil
      )
    end

    def parse_earnings_snapshot(data)
      EarningsSnapshot.new(
        today: data["today"],
        yesterday: data["yesterday"],
        this_week: data["this_week"],
        this_month: data["this_month"],
        total: data["total"],
        currency: data["currency"]
      )
    end

    def parse_device_temperature(data)
      DeviceTemperature.new(
        current: data["current"],
        max: data["max"],
        throttling: data["throttling"]
      )
    end

    def parse_earnings(data)
      Earnings.new(
        period: data["period"],
        start_date: data["start_date"],
        end_date: data["end_date"],
        amount: data["amount"],
        blocks: data["blocks"],
        shares: data["shares"],
        average_hashrate: data["average_hashrate"],
        currency: data["currency"],
        breakdown: (data["breakdown"] || []).map { |b| parse_earnings_breakdown(b) }
      )
    end

    def parse_earnings_breakdown(data)
      EarningsBreakdown.new(
        date: data["date"],
        amount: data["amount"],
        blocks: data["blocks"],
        shares: data["shares"],
        hashrate: data["hashrate"]
      )
    end

    def parse_mining_device(data)
      MiningDevice.new(
        id: data["id"],
        name: data["name"],
        type: data["type"],
        status: data["status"],
        hashrate: data["hashrate"],
        temperature: data["temperature"],
        fan_speed: data["fan_speed"],
        power_draw: data["power_draw"],
        memory_used: data["memory_used"],
        memory_total: data["memory_total"],
        driver: data["driver"],
        firmware: data["firmware"]
      )
    end

    def parse_worker_info(data)
      WorkerInfo.new(
        id: data["id"],
        name: data["name"],
        status: data["status"],
        hashrate: data["hashrate"] ? parse_hashrate(data["hashrate"]) : nil,
        shares: data["shares"] ? parse_share_stats(data["shares"]) : nil,
        devices: (data["devices"] || []).map { |d| parse_mining_device(d) },
        last_seen: data["last_seen"],
        uptime: data["uptime"]
      )
    end

    def parse_mining_algorithm(data)
      MiningAlgorithm.new(
        name: data["name"],
        display_name: data["display_name"],
        hash_unit: data["hash_unit"],
        profitability: data["profitability"],
        difficulty: data["difficulty"],
        block_reward: data["block_reward"],
        block_time: data["block_time"]
      )
    end
  end
end
