"""Tests for the CI generator."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.base import GeneratorContext
from prisme.generators.ci import CIGenerator
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec
from prisme.spec.project import CIConfig, ProjectSpec
from prisme.spec.stack import FileStrategy, StackSpec


@pytest.fixture
def basic_stack() -> StackSpec:
    return StackSpec(
        name="test-project",
        version="1.0.0",
        models=[
            ModelSpec(
                name="Item",
                fields=[FieldSpec(name="name", type=FieldType.STRING, required=True)],
            )
        ],
    )


@pytest.fixture
def ci_project_spec() -> ProjectSpec:
    return ProjectSpec(
        name="test-project",
        ci=CIConfig(
            provider="github",
            include_frontend=True,
            use_redis=True,
            enable_codecov=True,
            enable_dependabot=True,
            enable_semantic_release=True,
            enable_commitlint=True,
        ),
    )


@pytest.fixture
def ci_context(
    basic_stack: StackSpec, ci_project_spec: ProjectSpec, tmp_path: Path
) -> GeneratorContext:
    return GeneratorContext(
        domain_spec=basic_stack,
        output_dir=tmp_path,
        dry_run=True,
        project_spec=ci_project_spec,
    )


class TestCIGeneratorNoConfig:
    """Tests when CI config is absent."""

    def test_no_project_spec_returns_empty(self, basic_stack: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(domain_spec=basic_stack, output_dir=tmp_path, dry_run=True)
        gen = CIGenerator(ctx)
        assert gen.generate_files() == []

    def test_no_ci_config_returns_empty(self, basic_stack: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=basic_stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(name="test-project", ci=None),
        )
        gen = CIGenerator(ctx)
        assert gen.generate_files() == []

    def test_non_github_provider_returns_empty(
        self, basic_stack: StackSpec, tmp_path: Path
    ) -> None:
        ctx = GeneratorContext(
            domain_spec=basic_stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(name="test-project", ci=CIConfig(provider="gitlab")),
        )
        gen = CIGenerator(ctx)
        assert gen.generate_files() == []


class TestCIGeneratorFullConfig:
    """Tests for full CI generation with all features enabled."""

    def test_generates_ci_workflow(self, ci_context: GeneratorContext) -> None:
        gen = CIGenerator(ci_context)
        files = gen.generate_files()
        ci_file = next((f for f in files if f.path == Path(".github/workflows/ci.yml")), None)
        assert ci_file is not None
        assert ci_file.strategy == FileStrategy.GENERATE_ONCE
        assert "CI" in ci_file.content

    def test_generates_dependabot(self, ci_context: GeneratorContext) -> None:
        gen = CIGenerator(ci_context)
        files = gen.generate_files()
        dependabot = next((f for f in files if f.path == Path(".github/dependabot.yml")), None)
        assert dependabot is not None
        assert dependabot.strategy == FileStrategy.GENERATE_ONCE

    def test_generates_release_workflow(self, ci_context: GeneratorContext) -> None:
        gen = CIGenerator(ci_context)
        files = gen.generate_files()
        release = next((f for f in files if f.path == Path(".github/workflows/release.yml")), None)
        assert release is not None

    def test_generates_releaserc(self, ci_context: GeneratorContext) -> None:
        gen = CIGenerator(ci_context)
        files = gen.generate_files()
        releaserc = next((f for f in files if f.path == Path(".releaserc.json")), None)
        assert releaserc is not None

    def test_generates_changelog(self, ci_context: GeneratorContext) -> None:
        gen = CIGenerator(ci_context)
        files = gen.generate_files()
        changelog = next((f for f in files if f.path == Path("CHANGELOG.md")), None)
        assert changelog is not None

    def test_generates_commitlint(self, ci_context: GeneratorContext) -> None:
        gen = CIGenerator(ci_context)
        files = gen.generate_files()
        commitlint = next((f for f in files if f.path == Path("commitlint.config.js")), None)
        assert commitlint is not None

    def test_all_files_have_generate_once_strategy(self, ci_context: GeneratorContext) -> None:
        gen = CIGenerator(ci_context)
        files = gen.generate_files()
        for f in files:
            assert f.strategy == FileStrategy.GENERATE_ONCE

    def test_total_file_count(self, ci_context: GeneratorContext) -> None:
        gen = CIGenerator(ci_context)
        files = gen.generate_files()
        # ci.yml + dependabot.yml + release.yml + .releaserc.json + CHANGELOG.md + commitlint.config.js
        assert len(files) == 6


class TestCIGeneratorPartialConfig:
    """Tests for CI generation with partial features."""

    def test_no_dependabot(self, basic_stack: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=basic_stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                ci=CIConfig(provider="github", enable_dependabot=False),
            ),
        )
        gen = CIGenerator(ctx)
        files = gen.generate_files()
        assert not any(f.path == Path(".github/dependabot.yml") for f in files)

    def test_no_semantic_release(self, basic_stack: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=basic_stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                ci=CIConfig(provider="github", enable_semantic_release=False),
            ),
        )
        gen = CIGenerator(ctx)
        files = gen.generate_files()
        assert not any(f.path == Path(".github/workflows/release.yml") for f in files)
        assert not any(f.path == Path(".releaserc.json") for f in files)
        assert not any(f.path == Path("CHANGELOG.md") for f in files)

    def test_no_commitlint(self, basic_stack: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=basic_stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                ci=CIConfig(provider="github", enable_commitlint=False),
            ),
        )
        gen = CIGenerator(ctx)
        files = gen.generate_files()
        assert not any(f.path == Path("commitlint.config.js") for f in files)

    def test_minimal_ci_generates_only_workflow(
        self, basic_stack: StackSpec, tmp_path: Path
    ) -> None:
        ctx = GeneratorContext(
            domain_spec=basic_stack,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                ci=CIConfig(
                    provider="github",
                    enable_dependabot=False,
                    enable_semantic_release=False,
                    enable_commitlint=False,
                ),
            ),
        )
        gen = CIGenerator(ctx)
        files = gen.generate_files()
        assert len(files) == 1
        assert files[0].path == Path(".github/workflows/ci.yml")
