"""
Copyright (C) 2024-2026 Mo Zhou <lumin@debian.org>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""
import json
import os
import tempfile
from unittest.mock import patch

import pytest

from debgpt.cli import _resolve_resume_path, main


@pytest.mark.parametrize(
    "cmd",
    (
        "-F dryrun",
        "-F dryrun -Hf Makefile",
        "-F dryrun -Qf Makefile",
        "-F dryrun --invalid-argument-example man",
        "-F dryrun --man man",
        "-F dryrun --version",
        '-F dryrun --cmd "man man"',
    ),
)
def test_cli_system_exit(cmd: str):
    with pytest.raises(SystemExit):
        main(cmd.split())


def test_cli_backend_routing():
    with patch("debgpt.cli.subcmd_backend") as mock_subcmd:
        mock_subcmd.side_effect = SystemExit(0)
        with pytest.raises(SystemExit):
            main(["backend"])
        mock_subcmd.assert_called_once()


class TestResolveResumePath:
    """Tests for the _resolve_resume_path helper."""

    def test_explicit_path(self):
        """When a path is explicitly given, it is returned directly."""
        ag = type(
            "ag",
            (),
            {
                "json_file_path": "/tmp/some-session.json",
                "debgpt_home": "/tmp",
            },
        )()
        result = _resolve_resume_path(ag)
        assert result == "/tmp/some-session.json"

    def test_no_last_file(self):
        """When no path and no LAST file, exit(1) is called."""
        with tempfile.TemporaryDirectory() as tmpdir:
            ag = type(
                "ag",
                (),
                {
                    "json_file_path": None,
                    "debgpt_home": tmpdir,
                },
            )()
            with pytest.raises(SystemExit) as exc_info:
                _resolve_resume_path(ag)
            assert exc_info.value.code == 1

    def test_last_file_points_to_valid_json(self):
        """LAST file pointing to an existing JSON is resolved correctly."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create a session JSON file
            json_path = os.path.join(tmpdir, "test-uuid.json")
            with open(json_path, "wt") as f:
                json.dump([{"role": "user", "content": "hello"}], f)
            # Create LAST pointer
            last_path = os.path.join(tmpdir, "LAST")
            with open(last_path, "wt") as f:
                f.write("test-uuid.json\n")
            ag = type(
                "ag",
                (),
                {
                    "json_file_path": None,
                    "debgpt_home": tmpdir,
                },
            )()
            result = _resolve_resume_path(ag)
            assert result == json_path

    def test_last_file_points_to_missing_json(self):
        """LAST file pointing to a nonexistent JSON exits with error."""
        with tempfile.TemporaryDirectory() as tmpdir:
            last_path = os.path.join(tmpdir, "LAST")
            with open(last_path, "wt") as f:
                f.write("nonexistent.json\n")
            ag = type(
                "ag",
                (),
                {
                    "json_file_path": None,
                    "debgpt_home": tmpdir,
                },
            )()
            with pytest.raises(SystemExit) as exc_info:
                _resolve_resume_path(ag)
            assert exc_info.value.code == 1


class TestResumeSubcommand:
    """Integration tests for the resume subcommand."""

    def test_resume_with_explicit_path(self):
        """resume with a path loads the session and enters interactive mode."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create a session JSON file
            json_path = os.path.join(tmpdir, "resume-test.json")
            session = [
                {"role": "system", "content": "You are helpful."},
                {"role": "user", "content": "Hello"},
                {"role": "assistant", "content": "Hi there!"},
            ]
            with open(json_path, "wt") as f:
                json.dump(session, f)

            # Patch interact_with to avoid blocking
            with patch("debgpt.cli.frontend.interact_with") as mock_interact:
                main(["-F", "echo", "--debgpt_home", tmpdir, "resume", json_path])
                mock_interact.assert_called_once()

    def test_resume_with_last_file(self):
        """resume without path uses the LAST file in debgpt_home."""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Create a session JSON file
            json_path = os.path.join(tmpdir, "last-session.json")
            session = [
                {"role": "user", "content": "Question"},
                {"role": "assistant", "content": "Answer"},
            ]
            with open(json_path, "wt") as f:
                json.dump(session, f)
            # Create LAST pointer
            last_path = os.path.join(tmpdir, "LAST")
            with open(last_path, "wt") as f:
                f.write("last-session.json\n")

            with patch("debgpt.cli.frontend.interact_with") as mock_interact:
                main(["-F", "echo", "--debgpt_home", tmpdir, "resume"])
                mock_interact.assert_called_once()

    def test_resume_no_last_file_exits(self):
        """resume with no path and no LAST file exits with error."""
        with tempfile.TemporaryDirectory() as tmpdir:
            with pytest.raises(SystemExit) as exc_info:
                main(["-F", "echo", "--debgpt_home", tmpdir, "resume"])
            assert exc_info.value.code == 1
