"""
Tests for ETLOrchestrator and PipelineFactory (config-driven ETL entry points).
"""

import asyncio
from pathlib import Path

import pytest

from pycharter.etl_generator import (
    ETLOrchestrator,
    PipelineFactory,
    create_orchestrator,
)


class TestPipelineFactory:
    """Tests for PipelineFactory discovery and create_orchestrator."""

    def test_factory_scan_empty_dir(self, tmp_path):
        """PipelineFactory with empty root returns no pipelines."""
        factory = PipelineFactory(config_root=str(tmp_path))
        assert factory.get_pipeline_names() == []
        assert factory.get_contract_dir("any") is None

    def test_factory_scan_one_pipeline(self, tmp_path):
        """PipelineFactory discovers one pipeline when dir has extract.yaml and load.yaml."""
        pipeline_dir = tmp_path / "my_pipeline"
        pipeline_dir.mkdir()
        (pipeline_dir / "extract.yaml").write_text(
            "type: file\npath: /tmp/in.json\nformat: json\n"
        )
        (pipeline_dir / "load.yaml").write_text(
            "type: file\npath: /tmp/out.json\nformat: json\n"
        )
        factory = PipelineFactory(config_root=str(tmp_path))
        names = factory.get_pipeline_names()
        assert names == ["my_pipeline"]
        assert factory.get_contract_dir("my_pipeline") == str(pipeline_dir.resolve())
        orch = factory.create_orchestrator("my_pipeline", verbose=False)
        assert isinstance(orch, ETLOrchestrator)
        assert orch.contract_dir == pipeline_dir.resolve()

    def test_factory_excludes_templates_by_default(self, tmp_path):
        """PipelineFactory excludes 'templates' directory by default."""
        (tmp_path / "templates").mkdir()
        (tmp_path / "templates" / "extract.yaml").write_text(
            "type: file\npath: /tmp/x\nformat: json\n"
        )
        (tmp_path / "templates" / "load.yaml").write_text(
            "type: file\npath: /tmp/y\nformat: json\n"
        )
        factory = PipelineFactory(config_root=str(tmp_path))
        assert "templates" not in factory.get_pipeline_names()

    def test_factory_create_orchestrator_unknown_raises(self, tmp_path):
        """create_orchestrator with unknown name raises ValueError."""
        factory = PipelineFactory(config_root=str(tmp_path))
        with pytest.raises(ValueError, match="not found"):
            factory.create_orchestrator("nonexistent")


class TestETLOrchestrator:
    """Tests for ETLOrchestrator build and run return shape."""

    def test_create_orchestrator_helper(self, tmp_path):
        """create_orchestrator() returns ETLOrchestrator."""
        (tmp_path / "extract.yaml").write_text(
            "type: file\npath: /tmp/in.json\nformat: json\n"
        )
        (tmp_path / "load.yaml").write_text(
            "type: file\npath: /tmp/out.json\nformat: json\n"
        )
        orch = create_orchestrator(contract_dir=str(tmp_path), verbose=False)
        assert isinstance(orch, ETLOrchestrator)
        assert orch.contract_dir == tmp_path

    def test_orchestrator_requires_contract_dir_or_file_or_extract_load(self):
        """ETLOrchestrator raises when no config source given."""
        with pytest.raises(ValueError, match="extract_file and load_file"):
            ETLOrchestrator()

    def test_orchestrator_from_separate_files(self, tmp_path):
        """ETLOrchestrator can be created from extract_file, transform_file, load_file."""
        (tmp_path / "extract.yaml").write_text(
            "type: file\npath: /tmp/in.json\nformat: json\n"
        )
        (tmp_path / "transform.yaml").write_text(
            "transform:\n  - rename:\n      old: new\n"
        )
        (tmp_path / "load.yaml").write_text(
            "type: file\npath: /tmp/out.json\nformat: json\n"
        )
        orch = ETLOrchestrator(
            extract_file=str(tmp_path / "extract.yaml"),
            transform_file=str(tmp_path / "transform.yaml"),
            load_file=str(tmp_path / "load.yaml"),
            config_context={"X": "y"},
            verbose=False,
        )
        assert orch.extract_file == tmp_path / "extract.yaml"
        assert orch.load_file == tmp_path / "load.yaml"
        # Build pipeline (no run)
        p = orch._get_pipeline()
        assert p is not None
        assert p.name == "extract"  # default name from extract file stem

    @pytest.mark.asyncio
    async def test_orchestrator_run_returns_expected_shape(self, tmp_path):
        """ETLOrchestrator.run() returns dict with extraction, transformation, loading, success, error, failed_batches."""
        # Minimal file-based pipeline: read JSON array, write to file (dry_run still runs extract+transform)
        in_file = tmp_path / "in.json"
        in_file.write_text('[{"a": 1}, {"a": 2}]')
        out_file = tmp_path / "out.json"
        (tmp_path / "extract.yaml").write_text(
            f"type: file\npath: {in_file}\nformat: json\n"
        )
        (tmp_path / "load.yaml").write_text(
            f"type: file\npath: {out_file}\nformat: json\n"
        )
        orch = ETLOrchestrator(contract_dir=str(tmp_path), verbose=False)
        result = await orch.run(dry_run=True)
        assert "extraction" in result
        assert "transformation" in result
        assert "loading" in result
        assert "success" in result
        assert "error" in result
        assert "failed_batches" in result
        assert result["extraction"]["total_records"] == 2
        assert result["transformation"]["total_records"] == 2
        assert result["loading"]["total_records"] == 2
        assert result["success"] is True
        assert isinstance(result["failed_batches"], list)
