# Author::    Eric Crane  (mailto:eric.crane@mac.com)
# Copyright:: Copyright (c) 2024 Eric Crane.  All rights reserved.
#
# Helpers for getting and setting session data.
# 
# Resources:
#   https://www.rubydoc.info/gems/rack/1.5.5/Rack/Request#cookies-instance_method
#   https://rubydoc.info/github/rack/rack/Rack/Utils#set_cookie_header-class_method
#   https://en.wikipedia.org/wiki/HTTP_cookie
#   
require 'base64'

module Gloo
  module WebSvr
    class Session

      SESSION_CONTAINER = 'session'.freeze
      SESSION_ID_NAME = 'session_id'.freeze

      
      # ---------------------------------------------------------------------
      #    Initialization
      # ---------------------------------------------------------------------

      #
      # Set up the web server.
      #
      def initialize( engine, server_obj )
        @engine = engine
        @log = @engine.log

        @server_obj = server_obj
        @include_in_response = false
        @clearing_session = false
      end


      # ---------------------------------------------------------------------
      #    Set Session Data for Request
      # ---------------------------------------------------------------------

      # 
      # Get the session data from the encrypted cookie.
      # Add it to the session container.
      # 
      def set_session_data_for_request( env )
        begin
          cookie_hash = Rack::Utils.parse_cookies( env )

          # Are we using sessions?
          if @server_obj.use_session?
            data = cookie_hash[ session_name ]

            if data
              data = decode_decrypt( data ) 
              return unless data
              
              @session_id = data[ SESSION_ID_NAME ]

              data.each do |key, value|
                unless key == SESSION_ID_NAME
                  @server_obj.set_session_var( key, value )
                end
              end
            end
          end
        rescue => e
          @engine.log_exception e
        end
      end


      # ---------------------------------------------------------------------
      #    Set Session Data for Response
      # ---------------------------------------------------------------------

      # 
      # Temporarily set the flag to add the session data to the response.
      # Once this is done, the flag will be cleared and it will not
      # be added to the next request unless specifically set.
      # 
      def add_session_to_response
        @include_in_response = true
      end

      def init_session_id
        @session_id = Gloo::Objs::CsrfToken.generate_csrf_token
        return @session_id
      end

      # 
      # Initialize the session id and add it to the data.
      # Use the current session ID if it is there.
      # 
      def get_session_id
        if @clearing_session
          @clearing_session = false
          return nil
        end

        init_session_id if @session_id.blank?

        return @session_id
      end

      # 
      # Clear out the session Id.
      # Set the flag to add the session data to the response.
      # 
      def clear_session_data
        @session_id = nil
        @clearing_session = true
        add_session_to_response
      end

      # 
      # If there is session data, encrypt and add it to the response.
      # Once done, clear out the session data.
      # 
      def add_session_for_response( headers )
        # Are we using sessions?
        if @server_obj.use_session? && @include_in_response
          # Reset the flag because we are adding to the session data now
          @include_in_response = false

          # Build and add encrypted session data
          data = @server_obj.get_session_data
          data[ SESSION_ID_NAME ] = get_session_id

          unless data.empty?
            data = encrypt_encode( data )
            session_hash = { 
              value: data, 
              path: cookie_path, 
              expires: cookie_expires,
              http_only: true }

            if secure_cookie?
              session_hash[ :secure ] = true
            end

            Rack::Utils.set_cookie_header!( headers, session_name, session_hash )
          end
        end

        return headers
      end


      # ---------------------------------------------------------------------
      #    Helper functions
      # ---------------------------------------------------------------------

      # 
      # Encrypt and encode the session data.
      # 
      def encrypt_encode( data )
        return Gloo::Objs::Cipher.encrypt( data.to_json, key, iv )
      end

      # 
      # Decode and decrypt the session data.
      # 
      def decode_decrypt( data )
        return nil unless data && key && iv

        data = Gloo::Objs::Cipher.decrypt( data, key, iv )
        return JSON.parse( data )
      end

      # 
      # Get the session cookie name.
      # 
      def session_name
        return @server_obj.session_name
      end

      # 
      # Get the key for the encryption cipher.
      # 
      def key
        return @server_obj.encryption_key
      end

      # 
      # Get the initialization vector for the cipher.
      # 
      def iv
        return @server_obj.encryption_iv
      end

      # 
      # Get the path for the session cookie.
      # 
      def cookie_path
        return @server_obj.session_cookie_path
      end

      # 
      # Get the expiration time for the session cookie.
      # 
      def cookie_expires
        return @server_obj.session_cookie_expires
      end

      # 
      # Should the session cookie be secure?
      # 
      def secure_cookie?
        return @server_obj.session_cookie_secure
      end

    end
  end
end