# frozen_string_literal: true

require "fileutils"
require "json"

module Raif
  module Evals
    class Run
      attr_reader :eval_sets, :results, :output

      def initialize(file_paths: nil, output: $stdout)
        @output = output
        @results = {}

        @eval_sets = if file_paths&.any?
          load_eval_sets_from_files(file_paths)
        else
          discover_eval_sets
        end
      end

      def execute
        # Load setup file if it exists
        setup_file = Rails.root.join("raif_evals", "setup.rb")
        if File.exist?(setup_file)
          require setup_file
        else
          output.puts Raif::Utils::Colors.red("\n\nNo setup file found. To set up Raif evals, run:\n")
          output.puts Raif::Utils::Colors.red("bundle exec raif evals:setup\n")
          exit 1
        end

        output.puts "\nStarting Raif Eval Run"
        output.puts ""
        output.puts "Raif.config.default_llm_model_key: #{Raif.config.default_llm_model_key}"
        output.puts "Raif.config.evals_default_llm_judge_model_key: #{Raif.config.evals_default_llm_judge_model_key}"
        output.puts ""
        output.puts "=" * 50

        @eval_sets.each do |eval_set_entry|
          eval_set_class, file_path, line_number = if eval_set_entry.is_a?(Hash)
            [eval_set_entry[:class], eval_set_entry[:file_path], eval_set_entry[:line_number]]
          else
            [eval_set_entry, nil, nil]
          end

          if line_number
            # Running specific eval by line number
            output.puts "\nRunning #{eval_set_class.name} at line #{line_number}"
            output.puts "-" * 50

            eval_results = run_eval_at_line(eval_set_class, file_path, line_number)
          else
            # Running all evals in the set
            output.puts "\nRunning #{eval_set_class.name}"
            output.puts "-" * 50

            eval_results = eval_set_class.run(output: output)
          end

          @results[eval_set_class.name] = eval_results.map(&:to_h)
          passed_count = eval_results.count(&:passed?)
          total_count = eval_results.count

          output.puts "-" * 50
          output.puts "#{eval_set_class.name}: #{passed_count}/#{total_count} evals passed"
        end

        export_results
        print_summary
      end

    private

      def load_eval_sets_from_files(file_paths)
        eval_sets = []

        file_paths.each do |f|
          file_path = f[:file_path]
          line_number = f[:line_number]

          # Convert relative path to absolute
          absolute_path = File.expand_path(file_path)

          unless File.exist?(absolute_path)
            output.puts Raif::Utils::Colors.red("Error: File not found: #{file_path}")
            exit 1
          end

          subclasses_before = Raif::Evals::EvalSet.subclasses

          require absolute_path

          loaded_eval_sets = Raif::Evals::EvalSet.subclasses - subclasses_before
          eval_set_class = loaded_eval_sets.first

          eval_set_entry = { class: eval_set_class, file_path: absolute_path }
          eval_set_entry[:line_number] = line_number if line_number

          eval_sets << eval_set_entry
        end

        eval_sets
      end

      def run_eval_at_line(eval_set_class, file_path, line_number)
        target_eval = eval_set_class.evals.find{|e| e[:definition_line_number] == line_number }

        if target_eval.nil?
          output.puts Raif::Utils::Colors.red("Error: No eval block found at line #{line_number}")
          return []
        end

        instance = eval_set_class.new(output: output)
        [instance.run_eval(target_eval)]
      end

      def discover_eval_sets
        eval_sets_dir = Rails.root.join("raif_evals", "eval_sets")
        return [] unless eval_sets_dir.exist?

        Dir.glob(eval_sets_dir.join("**", "*_eval_set.rb")).map do |file|
          relative_path = Pathname.new(file).relative_path_from(Rails.root)
          require Rails.root.join(relative_path)

          # Extract the path components after raif_evals/eval_sets
          path_from_eval_sets = Pathname.new(file).relative_path_from(eval_sets_dir)
          path_parts = path_from_eval_sets.dirname.to_s.split("/")

          # Remove "." if it's the only element (meaning file is in eval_sets root)
          path_parts = [] if path_parts == ["."]

          # Build the full class name
          class_name = File.basename(file, ".rb").camelize
          namespace_parts = ["Raif", "Evals"] + path_parts.map(&:camelize)
          full_class_name = (namespace_parts + [class_name]).join("::")

          full_class_name.constantize
        end.select { |klass| klass < Raif::Evals::EvalSet }
      end

      def export_results
        results_dir = Rails.root.join("raif_evals", "results")
        FileUtils.mkdir_p(results_dir)

        timestamp = Time.current.strftime("%Y%m%d_%H%M%S")
        filename = results_dir.join("eval_run_#{timestamp}.json")

        File.write(filename, JSON.pretty_generate({
          run_at: Time.current.iso8601,
          results: @results,
          summary: summary_data
        }))

        output.puts "\nResults exported to: #{filename}"
      end

      def summary_data
        total_eval_sets = @results.count
        total_evals = @results.values.sum(&:count)
        passed_evals = @results.values.sum { |evals| evals.count { |e| e[:passed] } }

        total_expectations = @results.values.sum do |evals|
          evals.sum { |e| e[:expectation_results].count }
        end

        passed_expectations = @results.values.sum do |evals|
          evals.sum { |e| e[:expectation_results].count { |r| r[:status] == :passed } }
        end

        {
          total_eval_sets: total_eval_sets,
          total_evals: total_evals,
          passed_evals: passed_evals,
          total_expectations: total_expectations,
          passed_expectations: passed_expectations
        }
      end

      def print_summary
        data = summary_data

        output.puts ""
        output.puts "\n" + "=" * 50
        output.puts "SUMMARY"
        output.puts "=" * 50
        output.puts "Eval Sets: #{data[:total_eval_sets]}"
        output.puts ""
        output.puts "Evals:"
        output.puts "  #{data[:total_evals]} total"
        output.puts Raif::Utils::Colors.green("  #{data[:passed_evals]} passed")
        output.puts Raif::Utils::Colors.red("  #{data[:total_evals] - data[:passed_evals]} failed")
        output.puts ""
        output.puts "Expectations:"
        output.puts "  #{data[:total_expectations]} total"
        output.puts Raif::Utils::Colors.green("  #{data[:passed_expectations]} passed")
        output.puts Raif::Utils::Colors.red("  #{data[:total_expectations] - data[:passed_expectations]} failed")
        output.puts ""
      end
    end
  end
end
