# frozen_string_literal: true

require 'net/http'
require 'json'
require 'uri'
require 'base64'

module Synor
  module Compiler
    # Constants
    MAX_CONTRACT_SIZE = 256 * 1024
    MAX_MEMORY_PAGES = 16

    # Optimization levels
    module OptimizationLevel
      NONE = 'none'
      BASIC = 'basic'
      SIZE = 'size'
      AGGRESSIVE = 'aggressive'
    end

    # Strip options for WASM sections
    class StripOptions
      attr_accessor :strip_debug, :strip_producers, :strip_names,
                    :strip_custom, :preserve_sections, :strip_unused

      def initialize(
        strip_debug: true,
        strip_producers: true,
        strip_names: true,
        strip_custom: true,
        preserve_sections: [],
        strip_unused: true
      )
        @strip_debug = strip_debug
        @strip_producers = strip_producers
        @strip_names = strip_names
        @strip_custom = strip_custom
        @preserve_sections = preserve_sections
        @strip_unused = strip_unused
      end

      def to_h
        {
          strip_debug: @strip_debug,
          strip_producers: @strip_producers,
          strip_names: @strip_names,
          strip_custom: @strip_custom,
          preserve_sections: @preserve_sections,
          strip_unused: @strip_unused
        }
      end
    end

    # Compiler configuration
    class Config
      attr_accessor :api_key, :endpoint, :timeout, :retries, :debug,
                    :optimization_level, :strip_options, :max_contract_size,
                    :use_wasm_opt, :validate, :extract_metadata, :generate_abi

      def initialize(
        api_key:,
        endpoint: 'https://compiler.synor.io/v1',
        timeout: 60,
        retries: 3,
        debug: false,
        optimization_level: OptimizationLevel::SIZE,
        strip_options: nil,
        max_contract_size: MAX_CONTRACT_SIZE,
        use_wasm_opt: true,
        validate: true,
        extract_metadata: true,
        generate_abi: true
      )
        @api_key = api_key
        @endpoint = endpoint
        @timeout = timeout
        @retries = retries
        @debug = debug
        @optimization_level = optimization_level
        @strip_options = strip_options
        @max_contract_size = max_contract_size
        @use_wasm_opt = use_wasm_opt
        @validate = validate
        @extract_metadata = extract_metadata
        @generate_abi = generate_abi
      end
    end

    # Compiler error
    class CompilerError < StandardError
      attr_reader :code, :http_status

      def initialize(message, code: nil, http_status: nil)
        super(message)
        @code = code
        @http_status = http_status
      end
    end

    # Type information
    class TypeInfo
      attr_accessor :kind, :size, :element, :inner, :elements, :name, :fields

      def initialize(data)
        @kind = data['kind'] || 'unknown'
        @size = data['size']
        @element = data['element'] ? TypeInfo.new(data['element']) : nil
        @inner = data['inner'] ? TypeInfo.new(data['inner']) : nil
        @elements = data['elements']&.map { |e| TypeInfo.new(e) }
        @name = data['name']
        @fields = data['fields']
      end

      def type_name
        case @kind
        when 'u8', 'u16', 'u32', 'u64', 'u128', 'i8', 'i16', 'i32', 'i64', 'i128',
             'bool', 'string', 'bytes', 'address', 'hash256'
          @kind
        when 'fixedBytes'
          "bytes#{@size || 0}"
        when 'array'
          "#{@element&.type_name}[]"
        when 'fixedArray'
          "#{@element&.type_name}[#{@size || 0}]"
        when 'option'
          "#{@inner&.type_name}?"
        when 'tuple'
          "(#{@elements&.map(&:type_name)&.join(', ')})"
        when 'struct'
          @name || 'struct'
        else
          @name || 'unknown'
        end
      end
    end

    # Parameter ABI
    class ParamAbi
      attr_accessor :name, :type, :indexed

      def initialize(data)
        @name = data['name'] || ''
        @type = TypeInfo.new(data['type'] || { 'kind' => 'unknown' })
        @indexed = data['indexed'] || false
      end
    end

    # Function ABI
    class FunctionAbi
      attr_accessor :name, :selector, :inputs, :outputs, :view, :payable, :doc

      def initialize(data)
        @name = data['name'] || ''
        @selector = data['selector'] || ''
        @inputs = (data['inputs'] || []).map { |i| ParamAbi.new(i) }
        @outputs = (data['outputs'] || []).map { |o| ParamAbi.new(o) }
        @view = data['view'] || false
        @payable = data['payable'] || false
        @doc = data['doc']
      end
    end

    # Event ABI
    class EventAbi
      attr_accessor :name, :topic, :params, :doc

      def initialize(data)
        @name = data['name'] || ''
        @topic = data['topic'] || ''
        @params = (data['params'] || []).map { |p| ParamAbi.new(p) }
        @doc = data['doc']
      end
    end

    # Error ABI
    class ErrorAbi
      attr_accessor :name, :selector, :params, :doc

      def initialize(data)
        @name = data['name'] || ''
        @selector = data['selector'] || ''
        @params = (data['params'] || []).map { |p| ParamAbi.new(p) }
        @doc = data['doc']
      end
    end

    # Contract ABI
    class ContractAbi
      attr_accessor :name, :version, :functions, :events, :errors

      def initialize(data)
        @name = data['name'] || ''
        @version = data['version'] || '1.0.0'
        @functions = (data['functions'] || []).map { |f| FunctionAbi.new(f) }
        @events = (data['events'] || []).map { |e| EventAbi.new(e) }
        @errors = (data['errors'] || []).map { |e| ErrorAbi.new(e) }
      end

      def find_function(name)
        @functions.find { |f| f.name == name }
      end

      def find_by_selector(selector)
        @functions.find { |f| f.selector == selector }
      end
    end

    # Validation error
    class ValidationError
      attr_accessor :code, :message, :location

      def initialize(data)
        @code = data['code'] || ''
        @message = data['message'] || ''
        @location = data['location']
      end
    end

    # Validation result
    class ValidationResult
      attr_accessor :valid, :errors, :warnings, :export_count,
                    :import_count, :function_count, :memory_pages

      def initialize(data)
        @valid = data['valid'] || false
        @errors = (data['errors'] || []).map { |e| ValidationError.new(e) }
        @warnings = data['warnings'] || []
        @export_count = data['export_count'] || 0
        @import_count = data['import_count'] || 0
        @function_count = data['function_count'] || 0
        @memory_pages = data['memory_pages'] || [0, nil]
      end
    end

    # Contract metadata
    class ContractMetadata
      attr_accessor :name, :version, :authors, :description, :license,
                    :repository, :build_timestamp, :rust_version, :sdk_version, :custom

      def initialize(data)
        @name = data['name']
        @version = data['version']
        @authors = data['authors'] || []
        @description = data['description']
        @license = data['license']
        @repository = data['repository']
        @build_timestamp = data['build_timestamp']
        @rust_version = data['rust_version']
        @sdk_version = data['sdk_version']
        @custom = data['custom'] || {}
      end
    end

    # Compilation result
    class CompilationResult
      attr_accessor :contract_id, :code, :code_hash, :original_size, :optimized_size,
                    :size_reduction, :metadata, :abi, :estimated_deploy_gas,
                    :validation, :warnings

      def initialize(data)
        @contract_id = data['contract_id'] || ''
        @code = data['code'] || ''
        @code_hash = data['code_hash'] || ''
        @original_size = data['original_size'] || 0
        @optimized_size = data['optimized_size'] || 0
        @size_reduction = data['size_reduction'] || 0.0
        @metadata = data['metadata'] ? ContractMetadata.new(data['metadata']) : nil
        @abi = data['abi'] ? ContractAbi.new(data['abi']) : nil
        @estimated_deploy_gas = data['estimated_deploy_gas'] || 0
        @validation = data['validation'] ? ValidationResult.new(data['validation']) : nil
        @warnings = data['warnings'] || []
      end

      def size_stats
        "Original: #{@original_size} bytes, Optimized: #{@optimized_size} bytes, Reduction: #{format('%.1f', @size_reduction)}%"
      end

      def decode_code
        Base64.decode64(@code)
      end
    end

    # Size breakdown
    class SizeBreakdown
      attr_accessor :code, :data, :types, :functions, :memory,
                    :table, :exports, :imports, :custom, :total

      def initialize(data)
        @code = data['code'] || 0
        @data = data['data'] || 0
        @types = data['types'] || 0
        @functions = data['functions'] || 0
        @memory = data['memory'] || 0
        @table = data['table'] || 0
        @exports = data['exports'] || 0
        @imports = data['imports'] || 0
        @custom = data['custom'] || 0
        @total = data['total'] || 0
      end
    end

    # Function analysis
    class FunctionAnalysis
      attr_accessor :name, :size, :instruction_count, :local_count, :exported, :estimated_gas

      def initialize(data)
        @name = data['name'] || ''
        @size = data['size'] || 0
        @instruction_count = data['instruction_count'] || 0
        @local_count = data['local_count'] || 0
        @exported = data['exported'] || false
        @estimated_gas = data['estimated_gas'] || 0
      end
    end

    # Import analysis
    class ImportAnalysis
      attr_accessor :mod, :name, :kind, :signature

      def initialize(data)
        @mod = data['module'] || ''
        @name = data['name'] || ''
        @kind = data['kind'] || 'function'
        @signature = data['signature']
      end
    end

    # Security issue
    class SecurityIssue
      attr_accessor :severity, :type, :description, :location

      def initialize(data)
        @severity = data['severity'] || 'low'
        @type = data['type'] || ''
        @description = data['description'] || ''
        @location = data['location']
      end
    end

    # Security analysis
    class SecurityAnalysis
      attr_accessor :score, :issues, :recommendations

      def initialize(data)
        @score = data['score'] || 0
        @issues = (data['issues'] || []).map { |i| SecurityIssue.new(i) }
        @recommendations = data['recommendations'] || []
      end
    end

    # Gas analysis
    class GasAnalysis
      attr_accessor :deployment_gas, :function_gas, :memory_init_gas, :data_section_gas

      def initialize(data)
        @deployment_gas = data['deployment_gas'] || 0
        @function_gas = data['function_gas'] || {}
        @memory_init_gas = data['memory_init_gas'] || 0
        @data_section_gas = data['data_section_gas'] || 0
      end
    end

    # Contract analysis
    class ContractAnalysis
      attr_accessor :size_breakdown, :functions, :imports, :security, :gas_analysis

      def initialize(data)
        @size_breakdown = SizeBreakdown.new(data['size_breakdown'] || {})
        @functions = (data['functions'] || []).map { |f| FunctionAnalysis.new(f) }
        @imports = (data['imports'] || []).map { |i| ImportAnalysis.new(i) }
        @security = data['security'] ? SecurityAnalysis.new(data['security']) : nil
        @gas_analysis = data['gas_analysis'] ? GasAnalysis.new(data['gas_analysis']) : nil
      end
    end

    # Main compiler client
    class Client
      attr_reader :contracts, :abi, :analysis, :validation

      def initialize(config)
        @config = config
        @closed = false

        @contracts = ContractsClient.new(self)
        @abi = AbiClient.new(self)
        @analysis = AnalysisClient.new(self)
        @validation = ValidationClient.new(self)
      end

      def default_optimization_level
        @config.optimization_level
      end

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

      def info
        get('/info')
      end

      def close
        @closed = true
      end

      def closed?
        @closed
      end

      def compile(wasm, **options)
        wasm_base64 = Base64.strict_encode64(wasm)

        body = {
          wasm: wasm_base64,
          optimization_level: options[:optimization_level] || @config.optimization_level,
          use_wasm_opt: options.key?(:use_wasm_opt) ? options[:use_wasm_opt] : @config.use_wasm_opt,
          validate: options.key?(:validate) ? options[:validate] : @config.validate,
          extract_metadata: options.key?(:extract_metadata) ? options[:extract_metadata] : @config.extract_metadata,
          generate_abi: options.key?(:generate_abi) ? options[:generate_abi] : @config.generate_abi
        }

        body[:strip_options] = options[:strip_options].to_h if options[:strip_options]

        result = post('/compile', body)
        CompilationResult.new(result)
      end

      def get(path)
        check_closed!
        uri = URI("#{@config.endpoint}#{path}")
        request = Net::HTTP::Get.new(uri)
        execute_request(uri, request)
      end

      def post(path, body)
        check_closed!
        uri = URI("#{@config.endpoint}#{path}")
        request = Net::HTTP::Post.new(uri)
        request.body = body.to_json
        execute_request(uri, request)
      end

      private

      def execute_request(uri, request)
        request['Authorization'] = "Bearer #{@config.api_key}"
        request['Content-Type'] = 'application/json'
        request['X-SDK-Version'] = 'ruby/0.1.0'

        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = uri.scheme == 'https'
        http.read_timeout = @config.timeout
        http.open_timeout = @config.timeout

        response = http.request(request)
        handle_response(response)
      end

      def handle_response(response)
        body = JSON.parse(response.body)

        unless response.is_a?(Net::HTTPSuccess)
          raise CompilerError.new(
            body['message'] || "HTTP #{response.code}",
            code: body['code'],
            http_status: response.code.to_i
          )
        end

        body
      end

      def check_closed!
        raise CompilerError.new('Client has been closed', code: 'CLIENT_CLOSED') if @closed
      end
    end

    # Contracts sub-client
    class ContractsClient
      def initialize(client)
        @client = client
      end

      def compile(wasm, **options)
        @client.compile(wasm, **options)
      end

      def compile_dev(wasm)
        @client.compile(wasm, optimization_level: OptimizationLevel::NONE, use_wasm_opt: false, validate: true)
      end

      def compile_production(wasm)
        @client.compile(wasm,
                        optimization_level: OptimizationLevel::AGGRESSIVE,
                        use_wasm_opt: true,
                        validate: true,
                        extract_metadata: true,
                        generate_abi: true)
      end

      def get(contract_id)
        result = @client.get("/contracts/#{contract_id}")
        CompilationResult.new(result)
      end

      def list(limit: nil, offset: nil)
        params = []
        params << "limit=#{limit}" if limit
        params << "offset=#{offset}" if offset
        path = "/contracts#{params.empty? ? '' : "?#{params.join('&')}"}"

        result = @client.get(path)
        (result['contracts'] || []).map { |c| CompilationResult.new(c) }
      end

      def get_code(contract_id)
        result = @client.get("/contracts/#{contract_id}/code")
        Base64.decode64(result['code'] || '')
      end
    end

    # ABI sub-client
    class AbiClient
      def initialize(client)
        @client = client
      end

      def extract(wasm)
        wasm_base64 = Base64.strict_encode64(wasm)
        result = @client.post('/abi/extract', { wasm: wasm_base64 })
        ContractAbi.new(result)
      end

      def get(contract_id)
        result = @client.get("/contracts/#{contract_id}/abi")
        ContractAbi.new(result)
      end

      def encode_call(function_abi, args)
        result = @client.post('/abi/encode', {
                                function: {
                                  name: function_abi.name,
                                  selector: function_abi.selector,
                                  inputs: function_abi.inputs.map { |i| { name: i.name, type: { kind: i.type.kind } } },
                                  outputs: function_abi.outputs.map { |o| { name: o.name, type: { kind: o.type.kind } } }
                                },
                                args: args
                              })
        result['data'] || ''
      end

      def decode_result(function_abi, data)
        result = @client.post('/abi/decode', {
                                function: {
                                  name: function_abi.name,
                                  selector: function_abi.selector
                                },
                                data: data
                              })
        result['values'] || []
      end
    end

    # Analysis sub-client
    class AnalysisClient
      def initialize(client)
        @client = client
      end

      def analyze(wasm)
        wasm_base64 = Base64.strict_encode64(wasm)
        result = @client.post('/analysis', { wasm: wasm_base64 })
        ContractAnalysis.new(result)
      end

      def get(contract_id)
        result = @client.get("/contracts/#{contract_id}/analysis")
        ContractAnalysis.new(result)
      end

      def extract_metadata(wasm)
        wasm_base64 = Base64.strict_encode64(wasm)
        result = @client.post('/analysis/metadata', { wasm: wasm_base64 })
        ContractMetadata.new(result)
      end

      def estimate_deploy_gas(wasm)
        wasm_base64 = Base64.strict_encode64(wasm)
        result = @client.post('/analysis/estimate-gas', { wasm: wasm_base64 })
        result['gas'] || 0
      end

      def security_scan(wasm)
        wasm_base64 = Base64.strict_encode64(wasm)
        result = @client.post('/analysis/security', { wasm: wasm_base64 })
        SecurityAnalysis.new(result['security'] || {})
      end
    end

    # Validation sub-client
    class ValidationClient
      def initialize(client)
        @client = client
      end

      def validate(wasm)
        wasm_base64 = Base64.strict_encode64(wasm)
        result = @client.post('/validate', { wasm: wasm_base64 })
        ValidationResult.new(result)
      end

      def valid?(wasm)
        validate(wasm).valid
      end

      def errors(wasm)
        validate(wasm).errors.map(&:message)
      end

      def validate_exports(wasm, required_exports)
        wasm_base64 = Base64.strict_encode64(wasm)
        result = @client.post('/validate/exports', {
                                wasm: wasm_base64,
                                required_exports: required_exports
                              })
        result['valid'] || false
      end

      def validate_memory(wasm, max_pages)
        wasm_base64 = Base64.strict_encode64(wasm)
        result = @client.post('/validate/memory', {
                                wasm: wasm_base64,
                                max_pages: max_pages
                              })
        result['valid'] || false
      end
    end
  end
end
