# frozen_string_literal: true

require 'dry/struct'
require 'securerandom'
require 'twitch/audit/mixins/jsonify'
require 'twitch/audit/mixins/partial'
require 'twitch/audit/models/data_change'
require 'twitch/audit/models/types'

module Twitch
  module Audit
    # Structured data for audit client config information
    class LogEntry < Dry::Struct
      extend Mixins::Partial
      include Mixins::Jsonify

      # Action type enum
      ActionType = Types::Strict::String.enum('Read', 'Write', 'LogIn', 'LogOut')

      # @!group LogEntry metadata

      # @!attribute [r]
      # Milliseconds since unix epoch
      # @return [Integer]
      attribute :created, Types::Strict::Integer.default(&proc { Time.now.to_i * 1000 })

      # @!attribute [r]
      # Unique guid for this log entry
      # @return [String]
      attribute :id, Types::Strict::String.default(&proc { SecureRandom.uuid })

      # @!endgroup

      # @!group Application data

      # @!attribute [rw]
      # What type of action did the associated request perform?
      # @return [String]
      attribute :action_type, ActionType

      # @!attribute [rw]
      # A mapping of data changes performed by the associated request.
      # @return [String]
      attribute :data_changes, Types::Strict::Hash.map(Types::Strict::String, DataChange).meta(omittable: true)

      # @!attribute [rw]
      # Did an exception occur inside the associated request?
      # @return [String]
      attribute :exception, Types::Strict::Bool.default(false)

      # @!attribute [rw]
      # The name of the controller/handler that processed the associated request.
      # @return [String]
      attribute :handler, Types::Strict::String

      # @!attribute [rw]
      # Friendly name of the operation performed by the associated request.
      # @return [String]
      attribute :operation_name, Types::Strict::String

      # @!attribute [rw]
      # The list of roles allowed to issue the associated request.
      # @return [String]
      attribute :roles_allowed, Types::Strict::String

      # @!attribute [rw]
      # The service name for the consuming service.
      # @return [String]
      attribute :service, Types::Strict::String

      # @!endgroup

      # @!group JWT related data

      # @!attribute [rw]
      # Users amazon id, corresponds to 'twAmazonUID' from LDAP.
      attribute :amazon_uid, Types::Strict::String.meta(omittable: true)

      # @!attribute [rw]
      # Department the user belongs to, corresponds to 'department' from LDAP.
      # @return [String]
      attribute :department, Types::Strict::String.meta(omittable: true)

      # @!attribute [rw]
      # Distinguished name of the authenticated user, corresponds to 'dn' from LDAP.
      # @return [String]
      attribute :distinguished_name, Types::Strict::String.meta(omittable: true)

      # @!attribute [rw]
      # Users email address, corresponds to 'mail' from LDAP.
      # @return [String]
      attribute :email, Types::Strict::String.meta(omittable: true)

      # @!attribute [rw]
      # Manager the user reports to, corresponds to 'manager' from LDAP.
      # @return [String]
      attribute :manager, Types::Strict::String.meta(omittable: true)

      # @!attribute [rw]
      # Name of the authenticated user, corresponds to 'cn' from LDAP.
      # @return [String]
      attribute :name, Types::Strict::String.meta(omittable: true)

      # @!attribute [rw]
      # Users title, corresponds to 'title' from LDAP.
      # @return [String]
      attribute :title, Types::Strict::String.meta(omittable: true)

      # @!attribute [rw]
      # JWT unique identifier
      # @return [String]
      attribute :token_id, Types::Strict::String.meta(omittable: true)

      # @!endgroup

      # @!group HTTP Request related data

      # @!attribute [rw]
      # Was the associated request canceled?
      # @note Currently unaware of a way to determine this in a Rack environment
      # @return [String]
      attribute :canceled, Types::Strict::Bool.default(false)

      # @!attribute [rw]
      # Client ip address from the associated request.
      # @return [String]
      attribute :client_ip, Types::Strict::String

      # @!attribute [rw]
      # The post data from the associated request.
      # @note Remember to strip any sensitive data!
      # @return [String]
      attribute :data, Types::Strict::String

      # @!attribute [rw]
      # The method of the associated request.
      # @return [String]
      attribute :method, Types::Strict::String

      # @!attribute [rw]
      # The response code of the associated request.
      # @return [String]
      attribute :response_code, Types::Strict::Integer

      # @!attribute [rw]
      # The uri of the associated request.
      # @return [String]
      attribute :uri, Types::Strict::String

      # @!attribute [rw]
      # User agent from the associated request.
      # @return [String]
      attribute :user_agent, Types::Strict::String

      # @!endgroup

      # @!group Hydration Helpers

      # Hydrates request metadata based on the Rack Request and Response objects
      # @note Only for use with partial objects
      # @param [Rack::Request]  request
      # @param [Integer] status
      # @return [void]
      def hydrate_request_data(request, status)
        self.client_ip = request.ip
        self.data ||= request.params.to_json # only set if the application hasn't provided a redacted version
        self.method = request.request_method
        self.response_code = status
        self.uri = request.path
        self.user_agent = request.user_agent
      end

      # Hydrates session metadata based on a bouncer session token
      # @note Only for use with partial objects
      # @param [Hash, nil] session
      # @return [void]
      def hydrate_session_data(session)
        self.amazon_uid = session['sub'] || session['twAmazonUID']
        self.department = session['department']
        self.distinguished_name = session['dn']
        self.email = session['email'] || session['mail']
        self.manager = session['manager']
        self.name = session['name'] || session['cn']
        self.title = session['title']
        self.token_id = session['jti']
      end
      # @!endgroup
    end
  end
end
