from __future__ import annotations

from contextlib import redirect_stderr
import io
from pathlib import Path
import tempfile
import unittest

from supersonar.cli import build_parser, merge_cli_with_config, run_scan, validate_quality_gate_config
from supersonar.config import Config
from supersonar.security import resolve_enabled_rules


class CLITests(unittest.TestCase):
    def test_version_flag(self) -> None:
        parser = build_parser()
        with self.assertRaises(SystemExit) as exc:
            parser.parse_args(["--version"])
        self.assertEqual(exc.exception.code, 0)

    def test_validates_negative_gate_values(self) -> None:
        config = Config()
        config.quality_gate.max_issues = -1

        errors = validate_quality_gate_config(config)
        self.assertTrue(any("max_issues" in error for error in errors))

    def test_validates_invalid_fail_on_value(self) -> None:
        config = Config()
        config.quality_gate.fail_on = "blocker"  # type: ignore[assignment]

        errors = validate_quality_gate_config(config)
        self.assertTrue(any("fail_on" in error for error in errors))

    def test_validates_gate_new_only_requires_baseline(self) -> None:
        config = Config()
        config.quality_gate.only_new_issues = True

        errors = validate_quality_gate_config(config)
        self.assertTrue(any("only_new_issues" in error for error in errors))

    def test_security_only_flag_enables_security_mode(self) -> None:
        parser = build_parser()
        args = parser.parse_args(["scan", ".", "--security-only"])
        merged = merge_cli_with_config(args, Config())
        self.assertTrue(merged.scan.security_only)

    def test_pretty_flag_sets_pretty_output_format(self) -> None:
        parser = build_parser()
        args = parser.parse_args(["scan", ".", "--pretty"])
        merged = merge_cli_with_config(args, Config())
        self.assertEqual(merged.report.output_format, "pretty")

    def test_security_only_filters_enabled_rules(self) -> None:
        resolved = resolve_enabled_rules(["SS004", "SS001", "SS007"], security_only=True)
        self.assertEqual(resolved, ["SS001", "SS007"])

    def test_security_only_default_rules_include_non_python_security(self) -> None:
        resolved = resolve_enabled_rules(None, security_only=True)
        self.assertIsNotNone(resolved)
        assert resolved is not None
        self.assertIn("SS221", resolved)
        self.assertIn("SS306", resolved)
        self.assertIn("SS407", resolved)
        self.assertIn("SS507", resolved)
        self.assertIn("SS108", resolved)
        self.assertIn("SS111", resolved)

    def test_top_level_help_contains_manual(self) -> None:
        parser = build_parser()
        help_text = parser.format_help()
        self.assertIn("Security scanning (backend + frontend + infra):", help_text)
        self.assertIn("supersonar scan . --security-only", help_text)
        self.assertIn("supersonar scan -h", help_text)

    def test_run_scan_fails_for_missing_path(self) -> None:
        parser = build_parser()
        with tempfile.TemporaryDirectory() as tmp:
            config_path = Path(tmp) / "supersonar.toml"
            config_path.write_text("", encoding="utf-8")
            args = parser.parse_args(["scan", str(Path(tmp) / "does-not-exist"), "--config", str(config_path)])

            stderr = io.StringIO()
            with redirect_stderr(stderr):
                exit_code = run_scan(args)

        self.assertEqual(exit_code, 2)
        self.assertIn("[scan] Path not found:", stderr.getvalue())

    def test_run_scan_fails_gracefully_for_missing_coverage_xml(self) -> None:
        parser = build_parser()
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp) / "repo"
            root.mkdir()
            (root / "ok.py").write_text("print('ok')\n", encoding="utf-8")
            config_path = Path(tmp) / "supersonar.toml"
            config_path.write_text("", encoding="utf-8")
            args = parser.parse_args(
                [
                    "scan",
                    str(root),
                    "--config",
                    str(config_path),
                    "--coverage-xml",
                    str(root / "missing-coverage.xml"),
                ]
            )

            stderr = io.StringIO()
            with redirect_stderr(stderr):
                exit_code = run_scan(args)

        self.assertEqual(exit_code, 2)
        self.assertIn("[coverage]", stderr.getvalue())

    def test_run_scan_fails_gracefully_for_missing_baseline(self) -> None:
        parser = build_parser()
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp) / "repo"
            root.mkdir()
            (root / "ok.py").write_text("print('ok')\n", encoding="utf-8")
            config_path = Path(tmp) / "supersonar.toml"
            config_path.write_text("", encoding="utf-8")
            args = parser.parse_args(
                [
                    "scan",
                    str(root),
                    "--config",
                    str(config_path),
                    "--baseline-report",
                    str(root / "missing-baseline.json"),
                    "--gate-new-only",
                ]
            )

            stderr = io.StringIO()
            with redirect_stderr(stderr):
                exit_code = run_scan(args)

        self.assertEqual(exit_code, 2)
        self.assertIn("[baseline]", stderr.getvalue())

    def test_run_scan_emits_progress_when_forced(self) -> None:
        parser = build_parser()
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp) / "repo"
            root.mkdir()
            (root / "a.py").write_text("print('a')\n", encoding="utf-8")
            (root / "b.py").write_text("print('b')\n", encoding="utf-8")
            report_path = Path(tmp) / "report.json"
            config_path = Path(tmp) / "supersonar.toml"
            config_path.write_text("", encoding="utf-8")
            args = parser.parse_args(
                [
                    "scan",
                    str(root),
                    "--config",
                    str(config_path),
                    "--format",
                    "json",
                    "--out",
                    str(report_path),
                    "--progress",
                ]
            )

            stderr = io.StringIO()
            with redirect_stderr(stderr):
                exit_code = run_scan(args)

        self.assertEqual(exit_code, 0)
        self.assertIn("[progress] 2/2 100%", stderr.getvalue())

    def test_run_scan_skips_progress_by_default_when_stderr_is_not_tty(self) -> None:
        parser = build_parser()
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp) / "repo"
            root.mkdir()
            (root / "a.py").write_text("print('a')\n", encoding="utf-8")
            report_path = Path(tmp) / "report.json"
            config_path = Path(tmp) / "supersonar.toml"
            config_path.write_text("", encoding="utf-8")
            args = parser.parse_args(
                [
                    "scan",
                    str(root),
                    "--config",
                    str(config_path),
                    "--format",
                    "json",
                    "--out",
                    str(report_path),
                ]
            )

            stderr = io.StringIO()
            with redirect_stderr(stderr):
                exit_code = run_scan(args)

        self.assertEqual(exit_code, 0)
        self.assertNotIn("[progress]", stderr.getvalue())


if __name__ == "__main__":
    unittest.main()
