# frozen_string_literal: true

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

module SynorEconomics
  # Synor Economics SDK client for Ruby.
  # Pricing, billing, staking, and discount management.
  class Client
    attr_reader :closed

    def initialize(config)
      @config = config
      @closed = false
      @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

    # ==================== Pricing Operations ====================

    def get_pricing(service: nil)
      path = service ? "/pricing?service=#{service}" : "/pricing"
      response = get(path)
      (response["pricing"] || []).map { |p| parse_service_pricing(p) }
    end

    def get_price(service:, usage:)
      body = { service: service, usage: usage_to_hash(usage) }
      response = post("/pricing/calculate", body)
      parse_price_result(response)
    end

    def estimate_cost(plan:)
      body = { plan: plan.map { |p| usage_plan_item_to_hash(p) } }
      response = post("/pricing/estimate", body)
      parse_cost_estimate(response)
    end

    # ==================== Billing Operations ====================

    def get_usage(period: nil)
      path = period ? "/billing/usage?period=#{period}" : "/billing/usage"
      response = get(path)
      parse_usage(response)
    end

    def get_invoices
      response = get("/billing/invoices")
      (response["invoices"] || []).map { |i| parse_invoice(i) }
    end

    def get_invoice(invoice_id)
      response = get("/billing/invoices/#{encode(invoice_id)}")
      parse_invoice(response)
    end

    def get_balance
      response = get("/billing/balance")
      parse_account_balance(response)
    end

    # ==================== Staking Operations ====================

    def stake(amount:, duration_days: nil)
      body = { amount: amount }
      body[:duration_days] = duration_days if duration_days
      response = post("/staking/stake", body)
      parse_stake_receipt(response)
    end

    def unstake(stake_id)
      response = post("/staking/#{encode(stake_id)}/unstake", {})
      parse_unstake_receipt(response)
    end

    def get_stakes
      response = get("/staking")
      (response["stakes"] || []).map { |s| parse_stake(s) }
    end

    def get_stake(stake_id)
      response = get("/staking/#{encode(stake_id)}")
      parse_stake(response)
    end

    def get_staking_rewards
      response = get("/staking/rewards")
      parse_staking_rewards(response)
    end

    def claim_rewards
      response = post("/staking/rewards/claim", {})
      parse_stake_receipt(response)
    end

    # ==================== Discount Operations ====================

    def apply_discount(code)
      response = post("/discounts/apply", { code: code })
      parse_discount_result(response)
    end

    def remove_discount(code)
      delete("/discounts/#{encode(code)}")
    end

    def get_discounts
      response = get("/discounts")
      (response["discounts"] || []).map { |d| parse_discount(d) }
    end

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

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

    def close
      @closed = true
      @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 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_service_pricing(data)
      ServicePricing.new(
        service: data["service"],
        unit: data["unit"],
        price_per_unit: data["price_per_unit"],
        currency: data["currency"],
        minimum: data["minimum"],
        maximum: data["maximum"]
      )
    end

    def parse_price_result(data)
      PriceResult.new(
        service: data["service"],
        amount: data["amount"],
        currency: data["currency"],
        usage: data["usage"] ? parse_usage_metrics(data["usage"]) : nil
      )
    end

    def parse_usage_metrics(data)
      UsageMetrics.new(
        service: data["service"],
        compute_units: data["compute_units"],
        storage_bytes: data["storage_bytes"],
        bandwidth_bytes: data["bandwidth_bytes"],
        duration_seconds: data["duration_seconds"],
        requests: data["requests"]
      )
    end

    def parse_cost_estimate(data)
      CostEstimate.new(
        total: data["total"],
        currency: data["currency"],
        breakdown: (data["breakdown"] || []).map { |b| CostItem.new(service: b["service"], amount: b["amount"]) },
        discount_applied: data["discount_applied"],
        period: data["period"]
      )
    end

    def parse_usage(data)
      Usage.new(
        period: data["period"],
        start_date: data["start_date"],
        end_date: data["end_date"],
        items: (data["items"] || []).map { |i| parse_usage_item(i) },
        total_cost: data["total_cost"],
        currency: data["currency"]
      )
    end

    def parse_usage_item(data)
      UsageItem.new(
        service: data["service"],
        compute_units: data["compute_units"],
        storage_bytes: data["storage_bytes"],
        bandwidth_bytes: data["bandwidth_bytes"],
        requests: data["requests"],
        cost: data["cost"]
      )
    end

    def parse_invoice(data)
      Invoice.new(
        id: data["id"],
        date: data["date"],
        due_date: data["due_date"],
        status: data["status"],
        lines: (data["lines"] || []).map { |l| parse_invoice_line(l) },
        subtotal: data["subtotal"],
        discount: data["discount"],
        tax: data["tax"],
        total: data["total"],
        currency: data["currency"],
        pdf_url: data["pdf_url"]
      )
    end

    def parse_invoice_line(data)
      InvoiceLine.new(
        service: data["service"],
        description: data["description"],
        quantity: data["quantity"],
        unit_price: data["unit_price"],
        amount: data["amount"]
      )
    end

    def parse_account_balance(data)
      AccountBalance.new(
        available: data["available"],
        pending: data["pending"],
        staked: data["staked"],
        total: data["total"],
        currency: data["currency"]
      )
    end

    def parse_stake(data)
      Stake.new(
        id: data["id"],
        amount: data["amount"],
        staked_at: data["staked_at"],
        unlock_at: data["unlock_at"],
        status: data["status"],
        rewards_earned: data["rewards_earned"],
        apy: data["apy"]
      )
    end

    def parse_stake_receipt(data)
      StakeReceipt.new(
        id: data["id"],
        amount: data["amount"],
        tx_hash: data["tx_hash"],
        staked_at: data["staked_at"],
        unlock_at: data["unlock_at"],
        apy: data["apy"]
      )
    end

    def parse_unstake_receipt(data)
      UnstakeReceipt.new(
        id: data["id"],
        amount: data["amount"],
        tx_hash: data["tx_hash"],
        unstaked_at: data["unstaked_at"],
        available_at: data["available_at"]
      )
    end

    def parse_staking_rewards(data)
      StakingRewards.new(
        pending: data["pending"],
        claimed: data["claimed"],
        total: data["total"],
        current_apy: data["current_apy"],
        last_claim: data["last_claim"],
        next_distribution: data["next_distribution"]
      )
    end

    def parse_discount(data)
      Discount.new(
        code: data["code"],
        type: data["type"],
        value: data["value"],
        description: data["description"],
        applicable_services: data["applicable_services"],
        valid_from: data["valid_from"],
        valid_until: data["valid_until"],
        max_uses: data["max_uses"],
        current_uses: data["current_uses"],
        active: data["active"]
      )
    end

    def parse_discount_result(data)
      DiscountResult.new(
        discount: parse_discount(data["discount"]),
        savings_estimate: data["savings_estimate"],
        applied_at: data["applied_at"]
      )
    end

    # Conversion methods

    def usage_to_hash(usage)
      {
        service: usage.service,
        compute_units: usage.compute_units,
        storage_bytes: usage.storage_bytes,
        bandwidth_bytes: usage.bandwidth_bytes,
        duration_seconds: usage.duration_seconds,
        requests: usage.requests
      }
    end

    def usage_plan_item_to_hash(item)
      {
        service: item.service,
        projected_usage: usage_to_hash(item.projected_usage),
        period: item.period
      }
    end
  end
end
