import os
import pathlib
import sys
from unittest.mock import patch

import pytest
import typer

from rendercv.cli.render_command.progress_panel import ProgressPanel
from rendercv.cli.render_command.run_rendercv import (
    collect_input_file_paths,
    run_rendercv,
    timed_step,
)
from rendercv.exception import RenderCVUserError


class TestTimedStep:
    def test_returns_function_result(self):
        def sample_func(x: int) -> int:
            return x * 2

        progress = ProgressPanel(quiet=True)

        result = timed_step("Test", progress, sample_func, 5)

        assert result == 10

    def test_updates_progress_with_timing(self):
        def sample_func():
            return None

        progress = ProgressPanel(quiet=True)

        timed_step("Test message", progress, sample_func)

        assert len(progress.completed_steps) == 0

    def test_handles_single_path_result(self):
        def sample_func() -> pathlib.Path:
            return pathlib.Path.cwd() / "output.pdf"

        progress = ProgressPanel(quiet=True)

        result = timed_step("Generated PDF", progress, sample_func)

        assert result == pathlib.Path.cwd() / "output.pdf"
        assert len(progress.completed_steps) == 1
        assert progress.completed_steps[0].paths == [pathlib.Path.cwd() / "output.pdf"]

    def test_handles_list_path_result(self):
        def sample_func() -> list[pathlib.Path]:
            return [pathlib.Path.cwd() / "page1.png", pathlib.Path.cwd() / "page2.png"]

        progress = ProgressPanel(quiet=True)

        result = timed_step("Generated PNG", progress, sample_func)

        assert len(result) == 2
        assert len(progress.completed_steps) == 1
        assert progress.completed_steps[0].paths == [
            pathlib.Path.cwd() / "page1.png",
            pathlib.Path.cwd() / "page2.png",
        ]

    def test_pluralizes_message_for_multiple_paths(self):
        def sample_func() -> list[pathlib.Path]:
            return [pathlib.Path.cwd() / "page1.png", pathlib.Path.cwd() / "page2.png"]

        progress = ProgressPanel(quiet=True)

        timed_step("Generated PNG", progress, sample_func)

        assert progress.completed_steps[0].message == "Generated PNGs"

    def test_passes_args_and_kwargs_to_function(self):
        def sample_func(a: int, b: int, c: int = 0) -> int:
            return a + b + c

        progress = ProgressPanel(quiet=True)

        result = timed_step("Test", progress, sample_func, 1, 2, c=3)

        assert result == 6


class TestRunRendercv:
    def test_invalid_yaml(self, tmp_path):
        invalid_yaml = tmp_path / "invalid.yaml"
        invalid_yaml.write_text("invalid: yaml: content: :", encoding="utf-8")

        progress = ProgressPanel(quiet=True)

        with pytest.raises(typer.Exit) as exc_info, progress:
            run_rendercv(invalid_yaml, progress)

        assert exc_info.value.exit_code == 1

    def test_invalid_input_file(self, tmp_path):
        invalid_schema = tmp_path / "invalid_schema.yaml"
        invalid_schema.write_text("cv:\n  name: 123", encoding="utf-8")

        progress = ProgressPanel(quiet=True)

        with pytest.raises(typer.Exit) as exc_info, progress:
            run_rendercv(invalid_schema, progress)

        assert exc_info.value.exit_code == 1

    def test_template_syntax_error(self, tmp_path):
        os.chdir(tmp_path)

        theme_folder = tmp_path / "badtheme"
        theme_folder.mkdir()

        template_file = theme_folder / "Header.j2.typ"
        template_file.write_text(
            "{% for item in items %}\n{{ item }\n", encoding="utf-8"
        )

        yaml_file = tmp_path / "test.yaml"
        yaml_file.write_text(
            """cv:
    name: John Doe
design:
    theme: badtheme
""",
            encoding="utf-8",
        )

        progress = ProgressPanel(quiet=True)

        with pytest.raises(typer.Exit) as exc_info, progress:
            run_rendercv(yaml_file, progress)

        assert exc_info.value.exit_code == 1

    def test_user_error(self, tmp_path):
        yaml_file = tmp_path / "doesnt_exist.yaml"
        progress = ProgressPanel(quiet=True)
        with pytest.raises(typer.Exit) as _, progress:
            run_rendercv(yaml_file, progress)

    @pytest.mark.skipif(
        sys.platform == "win32", reason="chmod doesn't work the same on Windows"
    )
    def test_os_error_unreadable_file(self, tmp_path):
        """Test that OSError is properly caught when file is unreadable."""
        yaml_file = tmp_path / "unreadable.yaml"
        yaml_file.write_text("cv:\n  name: John Doe", encoding="utf-8")

        # Remove all permissions to make the file unreadable
        original_mode = yaml_file.stat().st_mode
        yaml_file.chmod(0o000)

        progress = ProgressPanel(quiet=True)

        try:
            with pytest.raises(typer.Exit) as exc_info, progress:
                run_rendercv(yaml_file, progress)

            assert exc_info.value.exit_code == 1
        finally:
            # Restore permissions for cleanup
            yaml_file.chmod(original_mode)

    def test_user_error_during_rendering(self, tmp_path):
        yaml_file = tmp_path / "test.yaml"
        yaml_file.write_text("cv:\n  name: John Doe\n", encoding="utf-8")
        progress = ProgressPanel(quiet=True)

        with (
            patch(
                "rendercv.cli.render_command.run_rendercv"
                ".build_rendercv_dictionary_and_model",
                side_effect=RenderCVUserError(message="test error"),
            ),
            pytest.raises(typer.Exit) as exc_info,
            progress,
        ):
            run_rendercv(yaml_file, progress)

        assert exc_info.value.exit_code == 1


class TestCollectInputFilePaths:
    def test_returns_only_input_file_by_default(self, tmp_path):
        yaml_file = tmp_path / "cv.yaml"
        yaml_file.write_text("cv:\n  name: John Doe\n", encoding="utf-8")

        result = collect_input_file_paths(yaml_file)

        assert result == {"input": yaml_file}

    def test_includes_cli_provided_files(self, tmp_path):
        yaml_file = tmp_path / "cv.yaml"
        yaml_file.write_text("cv:\n  name: John Doe\n", encoding="utf-8")
        design_file = tmp_path / "design.yaml"
        design_file.touch()
        settings_file = tmp_path / "settings.yaml"
        settings_file.touch()

        result = collect_input_file_paths(
            yaml_file, design=design_file, settings=settings_file
        )

        assert result["input"] == yaml_file
        assert result["design"] == design_file
        assert result["settings"] == settings_file

    def test_includes_yaml_referenced_files(self, tmp_path):
        design_file = tmp_path / "my_design.yaml"
        design_file.touch()

        yaml_file = tmp_path / "cv.yaml"
        yaml_file.write_text(
            "cv:\n  name: John Doe\n"
            "settings:\n  render_command:\n    design: my_design.yaml\n",
            encoding="utf-8",
        )

        result = collect_input_file_paths(yaml_file)

        assert result["input"] == yaml_file
        assert result["design"] == design_file.resolve()

    def test_invalid_yaml_still_returns_input_file(self, tmp_path):
        yaml_file = tmp_path / "cv.yaml"
        yaml_file.write_text("invalid: yaml: content: :", encoding="utf-8")

        result = collect_input_file_paths(yaml_file)

        assert result == {"input": yaml_file}

    def test_includes_cli_provided_locale_file(self, tmp_path):
        yaml_file = tmp_path / "cv.yaml"
        yaml_file.write_text("cv:\n  name: John Doe\n", encoding="utf-8")
        locale_file = tmp_path / "locale.yaml"
        locale_file.touch()

        result = collect_input_file_paths(yaml_file, locale=locale_file)

        assert result["input"] == yaml_file
        assert result["locale"] == locale_file

    def test_includes_yaml_referenced_locale_file(self, tmp_path):
        locale_file = tmp_path / "my_locale.yaml"
        locale_file.touch()

        yaml_file = tmp_path / "cv.yaml"
        yaml_file.write_text(
            "cv:\n  name: John Doe\n"
            "settings:\n  render_command:\n    locale: my_locale.yaml\n",
            encoding="utf-8",
        )

        result = collect_input_file_paths(yaml_file)

        assert result["input"] == yaml_file
        assert result["locale"] == locale_file.resolve()

    def test_cli_flags_take_precedence_over_yaml_references(self, tmp_path):
        yaml_ref_design = tmp_path / "yaml_design.yaml"
        yaml_ref_design.touch()
        cli_design = tmp_path / "cli_design.yaml"
        cli_design.touch()

        yaml_file = tmp_path / "cv.yaml"
        yaml_file.write_text(
            "cv:\n  name: John Doe\n"
            "settings:\n  render_command:\n    design: yaml_design.yaml\n",
            encoding="utf-8",
        )

        result = collect_input_file_paths(yaml_file, design=cli_design)

        assert result["design"] == cli_design
