module Jasmine
  class BrainService
    # Default intent keywords for hotel/motel business
    DEFAULT_INTENT_KEYWORDS = {
      price_question: %w[preço valor quanto custa fica tabela promoção pernoite diária],
      info_request: %w[como funciona detalhe explica suíte quarto tipo],
      policy: %w[horário check-in check-out política regra pagamento cancelamento],
      greeting: %w[oi olá bom dia boa tarde boa noite],
      objection: %w[caro não sei preciso pensar depois conversar],
      closing: %w[reservar agendar confirmar fechar quero sim pode],
      general: [] # fallback
    }.freeze

    # Strategies for handling intents
    RAG_MANDATORY = %i[price_question info_request policy].freeze
    RAG_OPTIONAL = %i[objection general].freeze
    RAG_PROHIBITED = %i[greeting closing].freeze

    attr_reader :inbox, :conversation, :message, :config

    def initialize(inbox:, conversation:, message:)
      @inbox = inbox
      @conversation = conversation
      @message = message
      @config = load_config
    end

    def respond
      intent = IntentDetector.new(message.content, intent_keywords).detect
      strategy = StrategyDecider.new(intent, jasmine_state).decide

      rag_context = fetch_rag_if_needed(strategy, message.content)

      prompt = PromptAssembler.new(
        config: config,
        state: jasmine_state,
        history: recent_history,
        rag_context: rag_context,
        current_message: message.content
      ).assemble

      response = call_llm(prompt)

      StateUpdater.new(conversation, intent, rag_context.present?).update

      log_decision(intent, strategy, rag_context)

      response
    rescue StandardError => e
      Rails.logger.error "[Jasmine::Brain] Error: #{e.message}"
      'Desculpe, tive um problema. Pode repetir?'
    end

    private

    def load_config
      inbox.jasmine_inbox_config || Jasmine::InboxConfig.new(
        rag_distance_threshold: ENV.fetch('DEFAULT_JASMINE_DISTANCE_THRESHOLD', 0.35).to_f,
        rag_max_results: 3,
        model: ENV.fetch('JASMINE_LLM_MODEL', 'gpt-4o-mini'),
        temperature: 0.7
      )
    end

    def intent_keywords
      custom = config.intent_keywords.presence || {}
      DEFAULT_INTENT_KEYWORDS.merge(custom.deep_symbolize_keys)
    end

    def jasmine_state
      conversation.custom_attributes&.dig('jasmine_state') || {}
    end

    def recent_history
      msgs = conversation.messages

      # Handle both ActiveRecord relations and arrays (for playground testing)
      return [] unless msgs.respond_to?(:where)

      msgs
        .where(message_type: %w[incoming outgoing])
        .order(created_at: :desc)
        .limit(4) # 2 pairs of messages
        .reverse
        .map { |m| { role: m.message_type == 'incoming' ? 'user' : 'assistant', content: m.content } }
    end

    def fetch_rag_if_needed(strategy, query)
      return nil if strategy == :no_rag
      return nil if loop_protection_triggered?

      results = Jasmine::SemanticSearchService.new(inbox).search(query, limit: config.rag_max_results)

      return nil if results.empty?

      results.map { |r| r[:content] || r.content }.join("\n\n---\n\n")
    end

    def loop_protection_triggered?
      (jasmine_state['rag_queries_count'] || 0) > 5
    end

    def fallback_response
      nil # Will trigger "vou verificar com a equipe"
    end

    def call_llm(prompt)
      chat = RubyLLM.chat(model: config.model).with_temperature(config.temperature)
      response = chat.ask(prompt)
      response.content
    end

    def log_decision(intent, strategy, rag_context)
      Rails.logger.info "[Jasmine::Brain] Intent: #{intent}, Strategy: #{strategy}, RAG: #{rag_context.present? ? 'yes' : 'no'}"
    end

    # =========================================
    # COMPONENT: Intent Detector
    # =========================================
    class IntentDetector
      attr_reader :text, :keywords

      def initialize(text, keywords)
        @text = text.to_s.downcase.strip
        @keywords = keywords
      end

      def detect
        # Check each intent type for keyword matches
        keywords.each do |intent_type, words|
          next if words.empty?
          return intent_type if words.any? { |word| text.include?(word.downcase) }
        end

        :general # fallback
      end
    end

    # =========================================
    # COMPONENT: Strategy Decider
    # =========================================
    class StrategyDecider
      attr_reader :intent, :state

      def initialize(intent, state)
        @intent = intent
        @state = state
      end

      def decide
        return :no_rag if RAG_PROHIBITED.include?(intent)
        return :rag_required if RAG_MANDATORY.include?(intent)

        :rag_optional
      end
    end

    # =========================================
    # COMPONENT: Prompt Assembler
    # =========================================
    class PromptAssembler
      MAX_HISTORY_MESSAGES = 4 # 2 pairs

      attr_reader :config, :state, :history, :rag_context, :current_message

      def initialize(config:, state:, history:, rag_context:, current_message:)
        @config = config
        @state = state
        @history = history.last(MAX_HISTORY_MESSAGES)
        @rag_context = rag_context
        @current_message = current_message
      end

      def assemble
        parts = []

        # System Prompt (identity, tone, rules)
        parts << "[INSTRUÇÕES DO SISTEMA]\n#{config.system_prompt}" if config.system_prompt.present?

        # Playbook SDR (sales script)
        parts << "[PLAYBOOK SDR]\n#{config.playbook_prompt}" if config.playbook_prompt.present?

        # Lead State
        if state.present?
          state_text = format_state(state)
          parts << "[ESTADO DO LEAD]\n#{state_text}"
        end

        # RAG Context (SOURCE OF TRUTH)
        if rag_context.present?
          parts << <<~RAG
            [CONTEXTO DA BASE DE CONHECIMENTO - FONTE DA VERDADE]
            Use EXCLUSIVAMENTE as informações abaixo para responder.
            NÃO invente ou complemente com conhecimento externo.

            #{rag_context}
          RAG
        end

        # History (limited)
        if history.present?
          history_text = history.map { |h| "#{h[:role] == 'user' ? 'Cliente' : 'Jasmine'}: #{h[:content]}" }.join("\n")
          parts << "[HISTÓRICO RECENTE]\n#{history_text}"
        end

        # Current message
        parts << "[MENSAGEM ATUAL DO CLIENTE]\n#{current_message}"

        parts.join("\n\n")
      end

      private

      def format_state(state)
        lines = []
        lines << "Etapa: #{state['stage']}" if state['stage']
        lines << "Qualificado: #{state['qualified'] ? 'Sim' : 'Não'}" if state.key?('qualified')

        if state['collected_info'].present?
          state['collected_info'].each do |key, value|
            lines << "#{key.capitalize}: #{value}"
          end
        end

        lines.join("\n")
      end
    end

    # =========================================
    # COMPONENT: State Updater
    # =========================================
    class StateUpdater
      MAX_STATE_SIZE = 4096 # 4KB

      attr_reader :conversation, :intent, :rag_used

      def initialize(conversation, intent, rag_used)
        @conversation = conversation
        @intent = intent
        @rag_used = rag_used
      end

      def update
        current_state = conversation.custom_attributes&.dig('jasmine_state') || {}

        new_state = current_state.merge(
          'last_intent' => intent.to_s,
          'rag_queries_count' => (current_state['rag_queries_count'] || 0) + (rag_used ? 1 : 0),
          'updated_at' => Time.current.iso8601
        )

        # Size protection
        if new_state.to_json.bytesize > MAX_STATE_SIZE
          Rails.logger.warn '[Jasmine::Brain] State too large, cleaning up'
          new_state = {
            'last_intent' => intent.to_s,
            'rag_queries_count' => 0,
            'updated_at' => Time.current.iso8601
          }
        end

        conversation.update!(
          custom_attributes: (conversation.custom_attributes || {}).merge('jasmine_state' => new_state)
        )
      end

      def self.cleanup(conversation)
        attrs = conversation.custom_attributes || {}
        attrs.delete('jasmine_state')
        conversation.update!(custom_attributes: attrs)
      end
    end
  end
end
