require 'socket'
require 'logger'
require 'securerandom'
require 'active_model'

require 'rubypitaya/core/path'
require 'rubypitaya/core/setup'
require 'rubypitaya/core/config'
require 'rubypitaya/core/session'
require 'rubypitaya/core/postman'
require 'rubypitaya/core/parameters'
require 'rubypitaya/core/http_routes'
require 'rubypitaya/core/route_error'
require 'rubypitaya/core/routes_base'
require 'rubypitaya/core/status_codes'
require 'rubypitaya/core/etcd_connector'
require 'rubypitaya/core/handler_router'
require 'rubypitaya/core/nats_connector'
require 'rubypitaya/core/service_holder'
require 'rubypitaya/core/instance_holder'
require 'rubypitaya/core/database_connector'
require 'rubypitaya/core/initializer_content'
require 'rubypitaya/core/initializer_broadcast'
require 'rubypitaya/core/application_files_importer'

module RubyPitaya

  class Main

    def initialize
      @setup = Setup.new
      @environment_name = @setup.fetch('rubypitaya.server.environment', 'development')
      @is_cheats_enabled = @setup.fetch('rubypitaya.server.cheats', false)
      @is_development_environment = @environment_name == 'development'
      @setup.auto_reload if @is_development_environment

      @log = Logger.new('/proc/self/fd/1')
      @log.level = Logger::INFO
      @log.formatter = proc do |severity, datetime, progname, msg|
        "#{msg}\n"
      end

      @application_files_importer = ApplicationFilesImporter.new
      @application_files_importer.import(@is_cheats_enabled)
      @application_files_importer.auto_reload if @is_development_environment

      @server_name = @setup['rubypitaya.server.name']
      @service_uuid = SecureRandom.uuid
      @desktop_name = Socket.gethostname

      @etcd_prefix = @setup['rubypitaya.etcd.prefix']
      @etcd_address = @setup['rubypitaya.etcd.url']
      @etcd_lease_seconds = @setup['rubypitaya.etcd.leaseSeconds']
      @allow_reconnect = false
      @etcd_connector = EtcdConnector.new(@service_uuid, @desktop_name,
                                          @server_name, @etcd_prefix,
                                          @etcd_address, @allow_reconnect,
                                          @etcd_lease_seconds, @log)
      @etcd_connector.connect

      @nats_address = @setup['rubypitaya.nats.url']
      @nats_connector = NatsConnector.new(@nats_address, @service_uuid,
                                          @server_name)

      @database_connector = DatabaseConnector.new(@setup, @log)
      @database_connector.connect

      @session = Session.new
      @postman = Postman.new(@nats_connector)
      @config = Config.new
      @config.auto_reload if @is_development_environment

      @services = ServiceHolder.new

      @initializer_content = InitializerContent.new(@log,
                                                    @services,
                                                    @setup,
                                                    @config)
      @initializer_broadcast = InitializerBroadcast.new
      @initializer_broadcast.run(@initializer_content)

      @handler_router = HandlerRouter.new(@is_cheats_enabled)

      connect_services

      run_http
      run_server
    end

    private

    def connect_services
      @services.all_services.map(&:connect)
    end

    def disconnect_services
      @services.all_services.map(&:disconnect)
    end

    def run_http
      HttpRoutes.set :log, @log
      HttpRoutes.set :services, @services
      HttpRoutes.set :setup, @setup
      HttpRoutes.set :config, @config
      HttpRoutes.set :views, [Path::HTTP_VIEWS_FOLDER_PATH] + Path::Plugins::HTTP_VIEWS_FOLDER_PATHS

      HttpRoutes.auto_reload if @is_development_environment

      Thread.new do
        HttpRoutes.bind = '0.0.0.0'
        HttpRoutes.port = '4567'
        HttpRoutes.run!
      end
    end

    def run_server
      @is_shutting_down = false

      catch :sig_shutdown do
        Signal.trap("SIGINT") { throw :sig_shutdown }
        Signal.trap("SIGQUIT") { throw :sig_shutdown }
        Signal.trap("SIGTERM") { throw :sig_shutdown }

        @log.info "Server started!"
        run_nats_connection
      end

      Signal.trap("SIGINT", nil)
      Signal.trap("SIGQUIT", nil)
      Signal.trap("SIGTERM", nil)

      shutdown_server
    end

    def shutdown_server
      return if @is_shutting_down
      @is_shutting_down = true

      @log.info "Server shutting down..."

      disconnect_services

      @etcd_connector.disconnect
      @nats_connector.disconnect
      @database_connector.disconnect

      exit(0)
    end

    def run_nats_connection
      loop do
        @nats_connector.connect do |request|
          start_time_seconds = Time.now.to_f

          message_route = request[:msg][:route]
          message_data = request[:msg][:data]
          message_json = JSON.parse(message_data) if !message_data.nil? && !message_data.empty?

          params = Parameters.new(message_json || {})

          session_id = request[:session][:id]
          session_uid = request[:session].fetch(:uid, '')
          session_data = request[:session][:data]
          session_data = JSON.parse(session_data, symbolize_names: true)
          frontend_id = request[:frontendID]
          metadata = request[:metadata]
          metadata = JSON.parse(metadata, symbolize_names: true)

          @session.update(session_id, session_uid, session_data, metadata,
                          frontend_id)

          handler_name, action_name = message_route.scan(/\A\w+\.(\w+)\.(\w+)\z/)[0]

          @log.info "request  -> route: #{message_route}"
          @log.info "         -> data:  #{message_data}"

          @config.clear_cache

          response = {}

          begin
            response = @handler_router.call(handler_name, action_name, @session,
                                            @postman, @services, @setup, @config,
                                            @log, params)
          rescue RouteError => error
            @log.error "ROUTE ERROR: #{error.code} | #{error.class} | #{error.message} \n #{error.backtrace.join("\n")}"
            response = {
              code: error.code,
              message: error.message
            }
          rescue Exception => error
            @log.error "INTERNAL ERROR: #{error.class} | #{error.message} \n #{error.backtrace.join("\n")}"
            response = {
              code: StatusCodes::CODE_INTERNAL_ERROR,
              message: StatusCodes::MESSAGE_INTERNAL_ERROR,
            }
          end

          response_json = JSON.generate(response)
          delta_time_seconds = ((Time.now.to_f - start_time_seconds) * 1000).round(2)

          @log.info "response [#{delta_time_seconds}ms] -> #{response_json}"

          response_json
        end
        rescue Exception => error
          @log.error "INTERNAL ERROR: #{error.class} | #{error.message}} \n #{error.backtrace.join("\n")}"
      end
    end
  end
end