# frozen_string_literal: true

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

module SynorHosting
  # Synor Hosting SDK client for Ruby.
  # Provides domain management, DNS, deployments, SSL, and analytics.
  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

    # ==================== Domain Operations ====================

    # Register a new domain
    def register_domain(name, auto_renew: true)
      body = { name: name, auto_renew: auto_renew }
      response = post("/domains", body)
      parse_domain(response)
    end

    # Get domain details
    def get_domain(name)
      response = get("/domains/#{encode(name)}")
      parse_domain(response)
    end

    # Resolve domain to its record
    def resolve_domain(name)
      response = get("/domains/#{encode(name)}/resolve")
      DomainRecord.new(
        cid: response["cid"],
        ttl: response["ttl"],
        updated_at: response["updated_at"]
      )
    end

    # Update domain record
    def update_domain(name, cid:, ttl: nil)
      body = { cid: cid }
      body[:ttl] = ttl if ttl
      response = put("/domains/#{encode(name)}", body)
      parse_domain(response)
    end

    # Transfer domain ownership
    def transfer_domain(name, new_owner)
      body = { new_owner: new_owner }
      response = post("/domains/#{encode(name)}/transfer", body)
      parse_domain(response)
    end

    # List domains
    def list_domains(limit: nil, offset: nil)
      params = {}
      params[:limit] = limit if limit
      params[:offset] = offset if offset
      response = get("/domains", params)
      (response["domains"] || []).map { |d| parse_domain(d) }
    end

    # ==================== DNS Operations ====================

    # Set DNS records for a domain
    def set_dns_records(domain, records)
      body = { records: records.map { |r| record_to_hash(r) } }
      response = put("/domains/#{encode(domain)}/dns", body)
      (response["records"] || []).map { |r| parse_dns_record(r) }
    end

    # Get DNS records for a domain
    def get_dns_records(domain)
      response = get("/domains/#{encode(domain)}/dns")
      (response["records"] || []).map { |r| parse_dns_record(r) }
    end

    # Add a single DNS record
    def add_dns_record(domain, record)
      body = record_to_hash(record)
      response = post("/domains/#{encode(domain)}/dns", body)
      parse_dns_record(response)
    end

    # Delete a DNS record
    def delete_dns_record(domain, record_type, name)
      delete("/domains/#{encode(domain)}/dns/#{record_type}/#{encode(name)}")
      nil
    end

    # ==================== Deployment Operations ====================

    # Deploy content to a domain
    def deploy(cid, domain, options = {})
      body = { cid: cid, domain: domain }
      body[:build_command] = options[:build_command] if options[:build_command]
      body[:env_vars] = options[:env_vars] if options[:env_vars]
      response = post("/deployments", body)
      parse_deployment(response)
    end

    # Get deployment details
    def get_deployment(id)
      response = get("/deployments/#{encode(id)}")
      parse_deployment(response)
    end

    # List deployments
    def list_deployments(domain: nil, limit: nil, offset: nil)
      params = {}
      params[:domain] = domain if domain
      params[:limit] = limit if limit
      params[:offset] = offset if offset
      response = get("/deployments", params)
      (response["deployments"] || []).map { |d| parse_deployment(d) }
    end

    # Wait for deployment to complete
    def wait_for_deployment(id, poll_interval: 5, max_wait: 600)
      deadline = Time.now + max_wait
      final_statuses = [DeploymentStatus::ACTIVE, DeploymentStatus::FAILED, DeploymentStatus::ROLLED_BACK]

      while Time.now < deadline
        deployment = get_deployment(id)
        return deployment if final_statuses.include?(deployment.status)
        sleep(poll_interval)
      end

      raise Error, "Timeout waiting for deployment"
    end

    # Rollback deployment
    def rollback(domain, deployment_id)
      body = { deployment_id: deployment_id }
      response = post("/domains/#{encode(domain)}/rollback", body)
      parse_deployment(response)
    end

    # ==================== SSL Operations ====================

    # Provision SSL certificate for a domain
    def provision_ssl(domain)
      response = post("/domains/#{encode(domain)}/ssl", {})
      parse_certificate(response)
    end

    # Get SSL certificate for a domain
    def get_certificate(domain)
      response = get("/domains/#{encode(domain)}/ssl")
      parse_certificate(response)
    end

    # Renew SSL certificate
    def renew_certificate(domain)
      response = post("/domains/#{encode(domain)}/ssl/renew", {})
      parse_certificate(response)
    end

    # ==================== Analytics Operations ====================

    # Get analytics for a domain
    def get_analytics(domain, start_time:, end_time:)
      params = { start: start_time.to_i, end: end_time.to_i }
      response = get("/domains/#{encode(domain)}/analytics", params)
      parse_analytics(response)
    end

    # Get bandwidth stats
    def get_bandwidth(domain, start_time:, end_time:)
      params = { start: start_time.to_i, end: end_time.to_i }
      response = get("/domains/#{encode(domain)}/analytics/bandwidth", params)
      BandwidthStats.new(
        total_bytes: response["total_bytes"],
        cached_bytes: response["cached_bytes"],
        uncached_bytes: response["uncached_bytes"]
      )
    end

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

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

    # Close the client
    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 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"]

      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

    def record_to_hash(record)
      hash = {
        type: record.type,
        name: record.name,
        value: record.value,
        ttl: record.ttl
      }
      hash[:priority] = record.priority if record.priority
      hash
    end

    def parse_domain(data)
      return nil unless data

      Domain.new(
        name: data["name"],
        owner: data["owner"],
        status: data["status"],
        record: data["record"] ? DomainRecord.new(
          cid: data["record"]["cid"],
          ttl: data["record"]["ttl"],
          updated_at: data["record"]["updated_at"]
        ) : nil,
        expires_at: data["expires_at"],
        created_at: data["created_at"],
        auto_renew: data["auto_renew"]
      )
    end

    def parse_dns_record(data)
      return nil unless data

      DnsRecord.new(
        type: data["type"],
        name: data["name"],
        value: data["value"],
        ttl: data["ttl"],
        priority: data["priority"]
      )
    end

    def parse_deployment(data)
      return nil unless data

      Deployment.new(
        id: data["id"],
        domain: data["domain"],
        cid: data["cid"],
        status: data["status"],
        url: data["url"],
        created_at: data["created_at"],
        deployed_at: data["deployed_at"],
        build_logs: data["build_logs"],
        error_message: data["error_message"]
      )
    end

    def parse_certificate(data)
      return nil unless data

      Certificate.new(
        domain: data["domain"],
        status: data["status"],
        issuer: data["issuer"],
        issued_at: data["issued_at"],
        expires_at: data["expires_at"],
        auto_renew: data["auto_renew"],
        fingerprint: data["fingerprint"]
      )
    end

    def parse_analytics(data)
      return nil unless data

      Analytics.new(
        domain: data["domain"],
        time_range: AnalyticsTimeRange.new(
          start_time: data["time_range"]&.dig("start"),
          end_time: data["time_range"]&.dig("end")
        ),
        requests: data["requests"] ? RequestStats.new(
          total: data["requests"]["total"],
          success: data["requests"]["success"],
          error: data["requests"]["error"],
          cached: data["requests"]["cached"]
        ) : nil,
        bandwidth: data["bandwidth"] ? BandwidthStats.new(
          total_bytes: data["bandwidth"]["total_bytes"],
          cached_bytes: data["bandwidth"]["cached_bytes"],
          uncached_bytes: data["bandwidth"]["uncached_bytes"]
        ) : nil,
        visitors: data["visitors"],
        page_views: data["page_views"],
        top_paths: (data["top_paths"] || []).map do |p|
          PathStats.new(path: p["path"], requests: p["requests"], bandwidth: p["bandwidth"])
        end,
        geo_distribution: (data["geo_distribution"] || []).map do |g|
          GeoStats.new(country: g["country"], requests: g["requests"], bandwidth: g["bandwidth"])
        end
      )
    end
  end
end
