require 'active_support/concern'

module Ooor

    DEFAULT_OOOR_SESSION_CONFIG_MAPPER = Proc.new do |env|
      Ooor.logger.debug "\n\nWARNING: using DEFAULT_OOOR_SESSION_CONFIG_MAPPER, you should probably define your own instead!
      You can define an Ooor::Rack.ooor_session_config_mapper block that will be evaled
      in the context of the rack middleware call after user is authenticated using Warden.
      Use it to map a Warden authentication to the OpenERP authentication you want.\n"""
      {}
    end

    DEFAULT_OOOR_PUBLIC_SESSION_CONFIG_MAPPER = DEFAULT_OOOR_SESSION_CONFIG_MAPPER

    DEFAULT_OOOR_ENV_DECORATOR = Proc.new do |env|
    end

    module RackBehaviour
      extend ActiveSupport::Concern
      module ClassMethods
        def ooor_session_config_mapper(&block)
          @ooor_session_config_mapper = block if block
          @ooor_session_config_mapper || DEFAULT_OOOR_SESSION_CONFIG_MAPPER
        end

        def ooor_public_session_config_mapper(&block)
          @ooor_public_session_config_mapper = block if block
          @ooor_public_session_config_mapper || DEFAULT_OOOR_PUBLIC_SESSION_CONFIG_MAPPER
        end

        def decorate_env(&block)
          @ooor_env_decorator = block if block
          @ooor_env_decorator || DEFAULT_OOOR_ENV_DECORATOR
        end
      end

      def set_ooor!(env)
        ooor_session = self.get_ooor_session(env)
        ooor_public_session = self.get_ooor_public_session(env)
        if defined?(I18n) && I18n.locale
          lang = Ooor::Locale.to_erp_locale(I18n.locale)
        elsif http_lang = env["HTTP_ACCEPT_LANGUAGE"]
          lang = http_lang.split(',')[0].gsub('-', '_')
        else
          lang = ooor_session.config['lang'] || 'en_US'
        end
        context = {'lang' => lang} #TODO also deal with timezone
        env['ooor'] = {'context' => context, 'ooor_session'=> ooor_session, 'ooor_public_session' => ooor_public_session}
        Ooor::Rack.decorate_env.call(env)
      end

      def get_ooor_public_session(env)
        config = Ooor.default_config.merge(Ooor::Rack.ooor_public_session_config_mapper.call(env))
        Ooor.session_handler.retrieve_session(config, :noweb)
      end

      def get_ooor_session(env)
        ruby_session_id = ::Rack::Request.new(env).session.id
        session = Ooor.session_handler.sessions[ruby_session_id]
        unless session # session could have been used by an other worker, try getting it
          config = Ooor.default_config.merge(Ooor::Rack.ooor_session_config_mapper.call(env))
          if config[:session_sharing] # same session_id as Odoo mode
            cookies_hash = env['rack.request.cookie_hash'] || ::Rack::Request.new(env).cookies
            spec = cookies_hash['session_id']
          else
            spec = ruby_session_id
          end
          web_session = Ooor.session_handler.get_web_session(spec) if spec # created by some other worker?
          web_session ||= {session_id: cookies_hash['session_id']} if config[:session_sharing]
          session = Ooor.session_handler.retrieve_session(config, spec, web_session)
          session.config[:params] = {email: env['warden'].try(:user).try(:email)}
        end
        session
      end

      def set_ooor_session!(env, status, headers, body)
        case headers["Set-Cookie"]
        when nil, ''
          headers["Set-Cookie"] = ""
        when Array
          headers["Set-Cookie"] = headers["Set-Cookie"].join("\n")
        end

        ooor_session = env['ooor']['ooor_session']
        if ooor_session.config[:session_sharing]
          share_openerp_session!(headers, ooor_session)
        end
        response = ::Rack::Response.new body, status, headers
        response.finish
      end

      def share_openerp_session!(headers, ooor_session)
        if ooor_session.config[:username] == 'admin'
          if ooor_session.config[:force_session_sharing]
            Ooor.logger.debug "Warning! force_session_sharing mode with admin user, this may be a serious security breach! Are you really in development mode?"
          else
            raise "Sharing OpenERP session for admin user is suicidal (use force_session_sharing in dev mode and be paranoiac about it)"
          end
        end
        cookie = ooor_session.web_session[:cookie]
        headers["Set-Cookie"] = [headers["Set-Cookie"], cookie].join("\n")

        if ooor_session.web_session[:sid] #v7
          session_id = ooor_session.web_session[:session_id]
          headers["Set-Cookie"] = [headers["Set-Cookie"],
            "instance0|session_id=%22#{session_id}%22; Path=/",
            "last_used_database=#{ooor_session.config[:database]}; Path=/",
            "session_id=#{session_id}; Path=/",
          ].join("\n")
        end
      end

    end

    class Rack
      include RackBehaviour

      attr_accessor :app, :env

      def initialize(app=nil)
        @app=app
      end

      def call(env)
        threadsafed = dup
        threadsafed.env = env
        threadsafed._call
      end

      def _call
        set_ooor!(env)
        status, headers, body = @app.call(env)
        set_ooor_session!(env, status, headers, body)
      end
    end

end
