"""Tests for ETL CLI commands."""

from __future__ import annotations

from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from pycharter.etl_generator.etl_cli import (
    cmd_etl_init,
    cmd_etl_inspect,
    cmd_etl_list,
    cmd_etl_run,
    cmd_etl_validate,
)

# Minimal YAML content for pipeline config files
_EXTRACT_YAML = "type: file\npath: /tmp/in.json\nformat: json\n"
_LOAD_YAML = "type: file\npath: /tmp/out.json\nformat: json\n"
_TRANSFORM_YAML = "transform:\n  - rename:\n      old: new\n"
_PIPELINE_YAML = (
    "name: test_pipeline\n"
    "extract:\n  type: file\n  path: /tmp/in.json\n  format: json\n"
    "load:\n  type: file\n  path: /tmp/out.json\n  format: json\n"
)


def _make_pipeline_dir(tmp_path: Path) -> Path:
    """Create a minimal pipeline directory."""
    d = tmp_path / "test_pipeline"
    d.mkdir()
    (d / "extract.yaml").write_text(_EXTRACT_YAML)
    (d / "load.yaml").write_text(_LOAD_YAML)
    return d


class TestCmdEtlValidate:
    """Tests for cmd_etl_validate."""

    def test_valid_directory(self, tmp_path):
        """Valid pipeline directory passes validation."""
        pipeline_dir = _make_pipeline_dir(tmp_path)
        assert cmd_etl_validate(str(pipeline_dir)) == 0

    def test_valid_file(self, tmp_path):
        """Valid pipeline file passes validation."""
        f = tmp_path / "pipeline.yaml"
        f.write_text(_PIPELINE_YAML)
        assert cmd_etl_validate(str(f)) == 0

    def test_invalid_path(self):
        """Nonexistent path returns exit code 1."""
        assert cmd_etl_validate("/nonexistent/path") == 1

    def test_invalid_config(self, tmp_path):
        """Invalid config returns exit code 1."""
        d = tmp_path / "bad"
        d.mkdir()
        (d / "extract.yaml").write_text("type: INVALID\n")
        (d / "load.yaml").write_text(_LOAD_YAML)
        assert cmd_etl_validate(str(d)) == 1


class TestCmdEtlInit:
    """Tests for cmd_etl_init."""

    def test_default_template(self, tmp_path):
        """Default template creates expected files."""
        target = tmp_path / "new_pipeline"
        result = cmd_etl_init(str(target), template="http-to-postgres")
        assert result == 0
        assert (target / "extract.yaml").exists()
        assert (target / "transform.yaml").exists()
        assert (target / "load.yaml").exists()
        assert (target / "settings.yaml").exists()
        assert (target / "variables.yaml").exists()

    def test_database_incremental_template(self, tmp_path):
        """database-incremental template creates files with incremental config."""
        target = tmp_path / "inc_pipeline"
        result = cmd_etl_init(str(target), template="database-incremental")
        assert result == 0
        assert (target / "extract.yaml").exists()
        # Check that extract has incremental config
        content = (target / "extract.yaml").read_text()
        assert "incremental" in content
        assert "watermark_field" in content

    def test_file_to_file_template(self, tmp_path):
        """file-to-file template creates expected files."""
        target = tmp_path / "file_pipeline"
        result = cmd_etl_init(str(target), template="file-to-file")
        assert result == 0
        assert (target / "extract.yaml").exists()
        assert (target / "load.yaml").exists()

    def test_unknown_template(self, tmp_path):
        """Unknown template returns exit code 1."""
        target = tmp_path / "bad"
        result = cmd_etl_init(str(target), template="nonexistent")
        assert result == 1

    def test_creates_nested_directory(self, tmp_path):
        """Init creates nested parent directories."""
        target = tmp_path / "a" / "b" / "c"
        result = cmd_etl_init(str(target), template="http-to-postgres")
        assert result == 0
        assert target.exists()


class TestCmdEtlList:
    """Tests for cmd_etl_list."""

    def test_lists_pipelines(self, tmp_path):
        """List discovers pipelines in nested dirs."""
        # PipelineFactory scans subdirectories of the root
        group = tmp_path / "group"
        group.mkdir()
        p1 = group / "pipeline_a"
        p1.mkdir()
        (p1 / "extract.yaml").write_text(_EXTRACT_YAML)
        (p1 / "load.yaml").write_text(_LOAD_YAML)

        p2 = group / "pipeline_b"
        p2.mkdir()
        (p2 / "extract.yaml").write_text(_EXTRACT_YAML)
        (p2 / "load.yaml").write_text(_LOAD_YAML)

        result = cmd_etl_list(str(tmp_path))
        assert result == 0

    def test_empty_directory(self, tmp_path):
        """Empty directory reports no pipelines."""
        result = cmd_etl_list(str(tmp_path))
        assert result == 0

    def test_nonexistent_directory(self):
        """Nonexistent directory returns exit code 1."""
        result = cmd_etl_list("/nonexistent/dir")
        assert result == 1

    def test_single_pipeline_dir(self, tmp_path):
        """Directory that is itself a pipeline shows as single result."""
        (tmp_path / "extract.yaml").write_text(_EXTRACT_YAML)
        (tmp_path / "load.yaml").write_text(_LOAD_YAML)
        result = cmd_etl_list(str(tmp_path))
        assert result == 0


class TestCmdEtlInspect:
    """Tests for cmd_etl_inspect."""

    def test_inspect_directory(self, tmp_path):
        """Inspect a pipeline directory."""
        pipeline_dir = _make_pipeline_dir(tmp_path)
        result = cmd_etl_inspect(str(pipeline_dir))
        assert result == 0

    def test_inspect_file(self, tmp_path):
        """Inspect a pipeline file."""
        f = tmp_path / "pipeline.yaml"
        f.write_text(_PIPELINE_YAML)
        result = cmd_etl_inspect(str(f))
        assert result == 0

    def test_inspect_with_transform(self, tmp_path):
        """Inspect a pipeline directory with transform."""
        pipeline_dir = _make_pipeline_dir(tmp_path)
        (pipeline_dir / "transform.yaml").write_text(_TRANSFORM_YAML)
        result = cmd_etl_inspect(str(pipeline_dir))
        assert result == 0

    def test_inspect_nonexistent(self):
        """Nonexistent path returns exit code 1."""
        result = cmd_etl_inspect("/nonexistent/path")
        assert result == 1

    def test_inspect_with_incremental(self, tmp_path):
        """Inspect shows incremental config."""
        pipeline_dir = _make_pipeline_dir(tmp_path)
        (pipeline_dir / "extract.yaml").write_text(
            "type: database\n"
            "query: SELECT * FROM users WHERE updated_at > '${__watermark__}'\n"
            "connection_string: sqlite:///test.db\n"
            "incremental:\n"
            "  enabled: true\n"
            "  watermark_field: updated_at\n"
            "  initial_value: '1970-01-01'\n"
        )
        result = cmd_etl_inspect(str(pipeline_dir))
        assert result == 0


class TestCmdEtlRun:
    """Tests for cmd_etl_run."""

    def test_nonexistent_path(self):
        """Nonexistent path returns exit code 1."""
        result = cmd_etl_run("/nonexistent/path")
        assert result == 1

    def test_run_with_variables(self, tmp_path):
        """Run passes variables correctly."""
        pipeline_dir = _make_pipeline_dir(tmp_path)

        # Mock asyncio.run and Pipeline
        mock_result = MagicMock()
        mock_result.success = True
        mock_result.pipeline_name = "test"
        mock_result.run_id = "abc"
        mock_result.duration_seconds = 1.0
        mock_result.rows_extracted = 10
        mock_result.rows_transformed = 10
        mock_result.rows_loaded = 10
        mock_result.rows_failed = 0
        mock_result.errors = []

        with patch(
            "pycharter.etl_generator.etl_cli.asyncio.run", return_value=mock_result
        ):
            result = cmd_etl_run(
                str(pipeline_dir),
                variables=["KEY=value"],
                dry_run=True,
            )

        assert result == 0

    def test_run_failure(self, tmp_path):
        """Run returns 1 when pipeline fails."""
        pipeline_dir = _make_pipeline_dir(tmp_path)

        mock_result = MagicMock()
        mock_result.success = False
        mock_result.pipeline_name = "test"
        mock_result.run_id = "abc"
        mock_result.duration_seconds = 1.0
        mock_result.rows_extracted = 0
        mock_result.rows_transformed = 0
        mock_result.rows_loaded = 0
        mock_result.rows_failed = 0
        mock_result.errors = ["Extraction failed"]

        with patch(
            "pycharter.etl_generator.etl_cli.asyncio.run", return_value=mock_result
        ):
            result = cmd_etl_run(str(pipeline_dir))

        assert result == 1

    def test_run_exception(self, tmp_path):
        """Run returns 1 when pipeline raises an exception."""
        pipeline_dir = _make_pipeline_dir(tmp_path)

        with patch(
            "pycharter.etl_generator.pipeline.Pipeline.from_config_dir",
            side_effect=ValueError("bad config"),
        ):
            result = cmd_etl_run(str(pipeline_dir))

        assert result == 1

    def test_run_single_file(self, tmp_path):
        """Run accepts a single pipeline file."""
        f = tmp_path / "pipeline.yaml"
        f.write_text(_PIPELINE_YAML)

        mock_result = MagicMock()
        mock_result.success = True
        mock_result.pipeline_name = "test"
        mock_result.run_id = "abc"
        mock_result.duration_seconds = 0.5
        mock_result.rows_extracted = 5
        mock_result.rows_transformed = 5
        mock_result.rows_loaded = 5
        mock_result.rows_failed = 0
        mock_result.errors = []

        with patch(
            "pycharter.etl_generator.etl_cli.asyncio.run", return_value=mock_result
        ):
            result = cmd_etl_run(str(f))

        assert result == 0
