require File.expand_path("../../../test_helper", __FILE__)
require "redmine_ai_helper/agents/issue_agent"

class RedmineAiHelper::Agents::IssueAgentTest < ActiveSupport::TestCase
  fixtures :projects, :users, :issues, :issue_statuses, :trackers, :enumerations

  context "IssueAgent" do
    setup do
      @project = Project.find(1)
      @user = User.find(1)
      @issue = Issue.find(1)
      @langfuse = RedmineAiHelper::LangfuseUtil::LangfuseWrapper.new(input: "Test input for Langfuse")
      @agent = RedmineAiHelper::Agents::IssueAgent.new(project: @project, langfuse: @langfuse)
    end

    should "generate backstory including issue properties" do
      backstory = @agent.backstory
      assert_match /issue properties are available/, backstory
      assert_match /Project ID: #{@project.id}/, backstory
    end

    should "include vector tools when vector db is enabled" do
      AiHelperSetting.any_instance.stubs(:vector_search_enabled).returns(true)
      tools = @agent.available_tool_providers
      assert_includes tools, RedmineAiHelper::Tools::VectorTools
      assert_includes tools, RedmineAiHelper::Tools::IssueTools
      assert_includes tools, RedmineAiHelper::Tools::ProjectTools
    end

    should "not include vector tools when vector db is disabled" do
      AiHelperSetting.any_instance.stubs(:vector_search_enabled).returns(false)
      tools = @agent.available_tool_providers
      assert_not_includes tools, RedmineAiHelper::Tools::VectorTools
      assert_includes tools, RedmineAiHelper::Tools::IssueTools
      assert_includes tools, RedmineAiHelper::Tools::ProjectTools
    end

    should "generate issue summary for visible issue" do
      @issue.stubs(:visible?).returns(true)

      # Set up mock prompt
      mock_prompt = mock("Prompt")
      mock_prompt.stubs(:format).returns("Summarize this issue")
      @agent.stubs(:load_prompt).with("issue_agent/summary").returns(mock_prompt)

      # Mock chat method
      @agent.stubs(:chat).returns("This is a summary of the issue.")

      result = @agent.issue_summary(issue: @issue)
      assert_equal "This is a summary of the issue.", result
    end

    should "deny access for non-visible issue" do
      @issue.stubs(:visible?).returns(false)
      result = @agent.issue_summary(issue: @issue)
      assert_equal "Permission denied", result
    end

    should "generate issue properties string" do
      RedmineAiHelper::Tools::IssueTools.any_instance.stubs(:capable_issue_properties).returns({
        "priority" => ["High", "Normal", "Low"],
        "status" => ["New", "In Progress", "Resolved"],
      })

      issue_properties = @agent.send(:issue_properties)

      assert_match /The following issue properties are available/, issue_properties
      assert_match /Project ID: #{@project.id}/, issue_properties
      assert_match /"priority"/, issue_properties
      assert_match /"status"/, issue_properties
    end

    context "generate_issue_reply" do
      should "generate reply for visible issue" do
        @issue.stubs(:visible?).returns(true)

        # Set up mock prompt
        mock_prompt = mock("Prompt")
        mock_prompt.stubs(:format).returns("Generate a reply for this issue")
        @agent.stubs(:load_prompt).with("issue_agent/generate_reply").returns(mock_prompt)

        # Mock chat method
        @agent.stubs(:chat).returns("This is a generated reply.")

        result = @agent.generate_issue_reply(issue: @issue, instructions: "Please provide a detailed response.")
        assert_equal "This is a generated reply.", result
      end

      should "deny access for non-visible issue" do
        @issue.stubs(:visible?).returns(false)
        result = @agent.generate_issue_reply(issue: @issue, instructions: "Please provide a detailed response.")
        assert_equal "Permission denied", result
      end
      should "format instructions correctly in the prompt" do
        @issue.stubs(:visible?).returns(true)
        setting = AiHelperProjectSetting.settings(@issue.project)
        setting.issue_draft_instructions = "Draft instructions for the issue."
        setting.save!
        mock_prompt = mock("Prompt")
        mock_prompt.expects(:format).with(
          issue: instance_of(String),
          instructions: "Please provide a detailed response.",
          issue_draft_instructions: "Draft instructions for the issue.",
          format: Setting.text_formatting,
        ).returns("Generate a reply for this issue with instructions.")
        @agent.stubs(:load_prompt).with("issue_agent/generate_reply").returns(mock_prompt)

        @agent.stubs(:chat).returns("This is a generated reply.")

        result = @agent.generate_issue_reply(issue: @issue, instructions: "Please provide a detailed response.")
        assert_equal "This is a generated reply.", result
      end
    end

    context "generate_sub_issues_draft" do
      setup do
        Langchain::OutputParsers::OutputFixingParser.stubs(:from_llm).returns(DummyFixParser.new)

        @agent.stubs(:chat).returns("This is a generated reply.")
        User.current = User.find(1)
      end
      should "generate sub issues for visible issue" do
        issue = Issue.find(1)

        subissues = @agent.generate_sub_issues_draft(issue: issue, instructions: "Create sub issues based on this issue.")

        assert subissues
      end
    end

    context "find_similar_issues" do
      setup do
        @mock_vector_tools = mock("VectorTools")
        RedmineAiHelper::Tools::VectorTools.stubs(:new).returns(@mock_vector_tools)
        @mock_setting = mock("AiHelperSetting")
        @mock_setting.stubs(:vector_search_enabled).returns(true)
        AiHelperSetting.stubs(:vector_search_enabled?).returns(true)
      end

      should "return empty array if issue not visible" do
        @issue.stubs(:visible?).returns(false)
        
        result = @agent.find_similar_issues(issue: @issue)
        
        assert_equal [], result
      end

      should "return empty array if vector search not enabled" do
        AiHelperSetting.stubs(:vector_search_enabled?).returns(false)
        
        result = @agent.find_similar_issues(issue: @issue)
        
        assert_equal [], result
      end

      should "call VectorTools with correct parameters" do
        @issue.stubs(:visible?).returns(true)
        similar_issues_data = [
          {
            id: 2,
            subject: "Similar issue",
            similarity_score: 85.0
          }
        ]
        
        @mock_vector_tools.expects(:find_similar_issues)
                         .with(issue_id: @issue.id, k: 10)
                         .returns(similar_issues_data)
        
        result = @agent.find_similar_issues(issue: @issue)
        
        assert_equal similar_issues_data, result
      end

      should "handle errors from VectorTools gracefully" do
        @issue.stubs(:visible?).returns(true)
        @mock_vector_tools.stubs(:find_similar_issues).raises(StandardError.new("Vector search failed"))
        
        # Should log error and re-raise (just check that logging happens)
        mock_logger = mock("logger")
        mock_logger.stubs(:error)  # Allow any error logging
        @agent.stubs(:ai_helper_logger).returns(mock_logger)
        
        assert_raises(StandardError) do
          @agent.find_similar_issues(issue: @issue)
        end
      end

      should "log debug message with results count" do
        @issue.stubs(:visible?).returns(true)
        similar_issues_data = [{id: 2}, {id: 3}]
        @mock_vector_tools.stubs(:find_similar_issues).returns(similar_issues_data)
        
        mock_logger = mock("logger")
        mock_logger.expects(:debug).with("Found 2 similar issues for issue #{@issue.id}")
        @agent.stubs(:ai_helper_logger).returns(mock_logger)
        
        result = @agent.find_similar_issues(issue: @issue)
        
        assert_equal similar_issues_data, result
      end
    end

    # Tests for refactoring - methods moved from llm.rb to IssueAgent
    context "text completion methods (refactored from llm.rb)" do
      setup do
        @project = Project.find(1)
        @issue = Issue.find(1)
        @user = User.find(1)
        User.current = @user
        @agent = RedmineAiHelper::Agents::IssueAgent.new(project: @project)
      end

      should "build completion context for description" do
        text = "This is a test description"
        context = @agent.send(:build_completion_context, text, "description", @project, @issue)
        
        assert_equal "description", context[:context_type]
        assert_equal @project.name, context[:project_name]
        assert_equal @issue.subject, context[:issue_title]
        assert_equal text.length, context[:text_length]
        assert_equal @project.description, context[:project_description]
        assert_equal @project.identifier, context[:project_identifier]
      end

      should "build completion context for note" do
        # Create some journals for the issue
        journal = Journal.create!(
          journalized: @issue,
          user: @user,
          notes: "This is a test note."
        )
        
        text = "Reply: "
        context = @agent.send(:build_completion_context, text, "note", @project, @issue)
        
        assert_equal "note", context[:context_type]
        assert_equal @project.name, context[:project_name]
        assert_equal @issue.subject, context[:issue_title]
        
        # Should include note-specific context
        assert context.key?(:issue_description)
        assert context.key?(:current_user_name)
        assert context.key?(:user_role_context)
      end

      should "build note specific context" do
        # Create some test data
        journal1 = Journal.create!(
          journalized: @issue,
          user: @user,
          notes: "First note"
        )
        journal2 = Journal.create!(
          journalized: @issue,
          user: User.find(2),
          notes: "Second note from another user"
        )
        
        context = @agent.send(:build_note_specific_context, @issue)
        
        assert_equal @issue.id, context[:issue_id]
        assert_equal @issue.subject, context[:issue_subject]
        assert_equal @user.name, context[:current_user_name]
        assert_equal @user.id, context[:current_user_id]
        assert context.key?(:recent_notes)
        assert context.key?(:user_role_context)
        
        # Check user role context
        role_context = context[:user_role_context]
        assert role_context.key?(:is_issue_author)
        assert role_context.key?(:is_assignee)
        assert role_context.key?(:suggested_role)
      end

      should "analyze user role in conversation" do
        # Mock issue data
        issue_data = {
          author: { id: @user.id, name: @user.name },
          assigned_to: { id: User.find(2).id, name: User.find(2).name }
        }
        
        journals = [
          { user: { id: @user.id, name: @user.name }, created_on: Time.current },
          { user: { id: User.find(2).id, name: User.find(2).name }, created_on: Time.current }
        ]
        
        role_info = @agent.send(:analyze_user_role_in_conversation, @user, journals, issue_data)
        
        assert role_info[:is_issue_author]
        assert_not role_info[:is_assignee]
        assert_equal 1, role_info[:participation_count]
        assert_equal "issue_author", role_info[:suggested_role]
      end

      should "parse completion response correctly" do
        # Test with normal text
        result = @agent.send(:parse_completion_response, "This is a suggestion.")
        assert_equal "This is a suggestion.", result
        
        # Test with markdown formatting
        result = @agent.send(:parse_completion_response, "**Bold** and *italic* text.")
        assert_equal "Bold and italic text.", result
        
        # Test with too many sentences
        long_text = "First sentence. Second sentence. Third sentence. Fourth sentence."
        result = @agent.send(:parse_completion_response, long_text)
        assert_equal "First sentence. Second sentence. Third sentence.", result
        
        # Test with empty/nil
        assert_equal "", @agent.send(:parse_completion_response, "")
        assert_equal "", @agent.send(:parse_completion_response, nil)
        
        # Test with code blocks
        result = @agent.send(:parse_completion_response, "```ruby\ncode here\n```\nSome text.")
        assert_equal "Some text.", result
      end

      should "generate text completion with proper template loading" do
        # Mock the prompt loading
        mock_prompt = mock("Prompt")
        mock_prompt.expects(:format).with(
          prefix_text: "Test",
          suffix_text: " completion",
          issue_title: @issue.subject,
          project_name: @project.name,
          cursor_position: "4",
          max_sentences: "3",
          format: Setting.text_formatting
        ).returns("Complete the text")
        
        @agent.expects(:load_prompt).with("issue_agent/inline_completion").returns(mock_prompt)
        @agent.expects(:chat).returns("This is the completion.")
        
        result = @agent.generate_text_completion(
          text: "Test completion",
          cursor_position: 4,
          context_type: "description",
          project: @project,
          issue: @issue
        )
        
        assert_equal "This is the completion.", result
      end

      should "generate text completion for note context" do
        # Create test journal
        Journal.create!(
          journalized: @issue,
          user: @user,
          notes: "Previous note"
        )
        
        # Mock the prompt loading for note completion
        mock_prompt = mock("Prompt")
        mock_prompt.expects(:format).returns("Complete the note")
        
        @agent.expects(:load_prompt).with("issue_agent/note_inline_completion").returns(mock_prompt)
        @agent.expects(:chat).returns("I agree with the analysis.")
        
        result = @agent.generate_text_completion(
          text: "Reply: I",
          cursor_position: 8,
          context_type: "note",
          project: @project,
          issue: @issue
        )
        
        assert_equal "I agree with the analysis.", result
      end

      should "handle errors gracefully in text completion" do
        @agent.expects(:load_prompt).raises(StandardError, "Template not found")
        
        result = @agent.generate_text_completion(
          text: "Test",
          cursor_position: 4,
          context_type: "description",
          project: @project,
          issue: @issue
        )
        
        assert_equal "", result
      end
    end
  end

  class DummyFixParser
    def parse(text)
      { "sub_issues" => [{ "subject" => "Dummy Sub Issue", "description" => "This is a dummy sub issue description." }] }
    end

    def get_format
      # Format is a simple string
      "string"
    end
  end
end
