require 'net/http'
require 'time'
require 'json'
require 'makitoo/feature_flag/version'
require 'makitoo/feature_flag/memory_cache'
require 'makitoo/feature_flag/user'

module Makitoo
  module FeatureFlag

    class Client

        def initialize(application_id, options = {})
          @logger = if options.has_key?(:logger)
            options[:logger]
          elsif defined?(Rails)
            Rails.logger
          else
            logger = Logger.new STDOUT
            logger.progname = 'Makitoo::FeatureFlag'
            logger
          end
          @logger.debug 'Initializing makitoo feature flag'
          @application_id = application_id
          @server = if options.has_key?(:server)
            options[:server]
          elsif options[:ssl]
            'https://features.makitoo.com/api/v1'
          else
            'http://features.makitoo.com/api/v1'
          end
          @cache = options[:cache] || MemoryCache.new @logger
          @environment_name = options[:environment_name] || 'production'
          @concurrency = options[:concurrency] || 1
          @force_sync = options[:force_sync] || @concurrency < 1
          start_background_threads
          @logger.debug 'Makitoo initialized'
        end

        def is_active(feature_name, user_or_id, default_state = false)
          @logger.debug 'Start is_active'
          feature_configuration = get_feature_configuration(feature_name, User.of(user_or_id))
          is_visible = if feature_configuration.nil?
            default_state
          else
            feature_configuration[:state] == 'ON'
          end
          event = if is_visible
            create_seen_event(feature_name, user)
          else
            create_skip_event(feature_name, user)
          end
          send_event(event)
          @logger.debug 'Stop is_active'
          is_visible
        end

        def success(feature_name, user_or_id)
          @logger.debug 'Start is_active'
          send_event(create_success_event(feature_name, User.of(user_or_id)))
          @logger.debug 'Stop success'
        end

        def track(event_name, user_or_id, properties = {})
          @logger.debug 'Start is_active'
          send_event(create_track_event(event_name, User.of(user_or_id), properties))
          @logger.debug 'Stop track'
        end

        def init_segment(segment_id, user_or_id)
          @logger.debug 'Start init_segment'
          post_json(@server + '/associate-foreign-id', {
            foreignId: segment_id,
            source: 'segment.com',
            installationId: User.of(user_or_id).id
          })
          @logger.debug 'Stop init_segment'
        end

      private

        def start_background_threads
          if !@force_sync
            @queue = Queue.new
            @logger.debug 'Background threads starting'
            (1..@concurrency).each do
              Thread.new do
                @logger.debug 'Background thread started'
                begin
                  loop do
                    begin
                      @logger.debug 'Async request waiting for task'
                      val = @queue.pop
                      @logger.debug 'Async request start'
                      res = Net::HTTP.start(val[:url].host, val[:url].port, use_ssl: val[:url].scheme == 'https') {|http|
                        http.request(val[:req])
                      }
                      @logger.debug 'Async request end'
                    rescue Exception => e
                      @logger.debug 'Error !!!'
                      log_error(e)
                    end
                  end
                ensure
                  @logger.debug 'Background thread stopped'
                end
              end
            end
          end
        end

        def log_error(e)
          @logger.error 'Exception'
          @logger.error e.message
          e.backtrace.each { |line| @logger.error line }
        end

        def refresh_feature_configurations_cache(user)
          feature_configurations = send_event(create_refresh_event(user), false)
          @cache.put('features.' + user.id, feature_configurations)
          feature_configurations
        end
        
        def get_feature_configuration(feature_name, user)
          feature_configurations = @cache.get('features.' + user.id)
          if feature_configurations.nil?
            feature_configurations = refresh_feature_configurations_cache(user)
          end
          if feature_configurations.nil?
            nil
          else
            feature_configurations[feature_name]
          end
        end

        def create_seen_event(feature_name, user)
          create_execution_event(feature_name, 'SEEN', user)
        end

        def create_skip_event(feature_name, user)
          create_execution_event(feature_name, 'SKIP', user)
        end

        def create_success_event(feature_name, user)
          create_execution_event(feature_name, 'SUCCESS', user)
        end

        def create_execution_event(feature_name, event_type, user)
          event = create_event(event_type, user)
          event[:featureName] = feature_name
          event
        end

        def create_track_event(event_name, user, properties)
          event = create_event('TRACK', user)
          event[:eventName] = event_name
          event[:properties] = properties
          event
        end

        def create_refresh_event(user)
          create_event('REFRESH', user)
        end

        def create_event(event_type, user)
          {
            eventType: event_type,
            date: Time.now().iso8601,
            count: 1,
            installationId: user.id,
            context: create_context(user),
            lib: {
              version: '0.0.1',
              type: 'ruby'
            }
          }
        end

        def post_json(url_str, data, async)
          url = URI.parse(url_str)
          req = Net::HTTP::Post.new(url.to_s, 'Content-Type' => 'application/json')
          req.body = data.to_json
          if async && !@force_sync
            @logger.debug 'Queue async event'
            @queue << {url: url, req: req}
            nil
          else
            @logger.debug 'Sync request start'
            res = Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') {|http|
              http.request(req)
            }
            @logger.debug 'Sync request end'
            res.body
          end
        end

        def send_event(event, async = true)
          begin
            body = post_json(@server + '/event?' + URI.encode_www_form({
              application: @application_id,
              environmentName: @environment_name
            }), event, async)
            if !body.nil?
              result = JSON.parse(body);
              feature_configurations = {}
              result['features'].each{|feature|
                feature_configurations[feature['name']] = {
                  name: feature['name'],
                  state: feature['state']
                }
              }
              feature_configurations
            end
          rescue => e
            log_error(e)
          end
        end

        def create_context(user)
          {
            user: user.as_json
          }
        end
    end
  end
end