"""
Unit tests for api/job_manager.py - Job lifecycle management.

Coverage target: 95%+

Tests verify:
- Job creation and validation
- Status transitions (state machine)
- Progress tracking
- Concurrency control
- Job retrieval and queries
- Cleanup operations
"""

import pytest
import json
from datetime import datetime, timedelta


@pytest.mark.unit
class TestJobCreation:
    """Test job creation and validation."""

    def test_create_job_success(self, clean_db):
        """Should create job with pending status."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job(
            config_path="configs/test.json",
            date_range=["2025-01-16", "2025-01-17"],
            models=["gpt-5", "claude-3.7-sonnet"]
        )

        assert job_id is not None
        job = manager.get_job(job_id)
        assert job["status"] == "pending"
        assert job["date_range"] == ["2025-01-16", "2025-01-17"]
        assert job["models"] == ["gpt-5", "claude-3.7-sonnet"]
        assert job["created_at"] is not None

    def test_create_job_with_job_details(self, clean_db):
        """Should create job_details for each model-day."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job(
            config_path="configs/test.json",
            date_range=["2025-01-16", "2025-01-17"],
            models=["gpt-5"]
        )

        progress = manager.get_job_progress(job_id)
        assert progress["total_model_days"] == 2  # 2 dates × 1 model
        assert progress["completed"] == 0
        assert progress["failed"] == 0

    def test_create_job_blocks_concurrent(self, clean_db):
        """Should prevent creating second job while first is pending."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job1_id = manager.create_job(
            "configs/test.json",
            ["2025-01-16"],
            ["gpt-5"]
        )

        with pytest.raises(ValueError, match="Another simulation job is already running"):
            manager.create_job(
                "configs/test.json",
                ["2025-01-17"],
                ["gpt-5"]
            )

    def test_create_job_after_completion(self, clean_db):
        """Should allow new job after previous completes."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job1_id = manager.create_job(
            "configs/test.json",
            ["2025-01-16"],
            ["gpt-5"]
        )

        manager.update_job_status(job1_id, "completed")

        # Now second job should be allowed
        job2_id = manager.create_job(
            "configs/test.json",
            ["2025-01-17"],
            ["gpt-5"]
        )
        assert job2_id is not None


@pytest.mark.unit
class TestJobStatusTransitions:
    """Test job status state machine."""

    def test_pending_to_running(self, clean_db):
        """Should transition from pending to running."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job(
            "configs/test.json",
            ["2025-01-16"],
            ["gpt-5"]
        )

        # Update detail to running
        manager.update_job_detail_status(job_id, "2025-01-16", "gpt-5", "running")

        job = manager.get_job(job_id)
        assert job["status"] == "running"
        assert job["started_at"] is not None

    def test_running_to_completed(self, clean_db):
        """Should transition to completed when all details complete."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job(
            "configs/test.json",
            ["2025-01-16"],
            ["gpt-5"]
        )

        manager.update_job_detail_status(job_id, "2025-01-16", "gpt-5", "running")
        manager.update_job_detail_status(job_id, "2025-01-16", "gpt-5", "completed")

        job = manager.get_job(job_id)
        assert job["status"] == "completed"
        assert job["completed_at"] is not None
        assert job["total_duration_seconds"] is not None

    def test_partial_completion(self, clean_db):
        """Should mark as partial when some models fail."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job(
            "configs/test.json",
            ["2025-01-16"],
            ["gpt-5", "claude-3.7-sonnet"]
        )

        # First model succeeds
        manager.update_job_detail_status(job_id, "2025-01-16", "gpt-5", "running")
        manager.update_job_detail_status(job_id, "2025-01-16", "gpt-5", "completed")

        # Second model fails
        manager.update_job_detail_status(job_id, "2025-01-16", "claude-3.7-sonnet", "running")
        manager.update_job_detail_status(
            job_id, "2025-01-16", "claude-3.7-sonnet", "failed",
            error="API timeout"
        )

        job = manager.get_job(job_id)
        assert job["status"] == "partial"

        progress = manager.get_job_progress(job_id)
        assert progress["completed"] == 1
        assert progress["failed"] == 1


@pytest.mark.unit
class TestJobRetrieval:
    """Test job query operations."""

    def test_get_nonexistent_job(self, clean_db):
        """Should return None for nonexistent job."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job = manager.get_job("nonexistent-id")
        assert job is None

    def test_get_current_job(self, clean_db):
        """Should return most recent job."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job1_id = manager.create_job("configs/test.json", ["2025-01-16"], ["gpt-5"])
        manager.update_job_status(job1_id, "completed")

        job2_id = manager.create_job("configs/test.json", ["2025-01-17"], ["gpt-5"])

        current = manager.get_current_job()
        assert current["job_id"] == job2_id

    def test_get_current_job_empty(self, clean_db):
        """Should return None when no jobs exist."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        current = manager.get_current_job()
        assert current is None

    def test_find_job_by_date_range(self, clean_db):
        """Should find existing job with same date range."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job(
            "configs/test.json",
            ["2025-01-16", "2025-01-17"],
            ["gpt-5"]
        )

        found = manager.find_job_by_date_range(["2025-01-16", "2025-01-17"])
        assert found["job_id"] == job_id

    def test_find_job_by_date_range_not_found(self, clean_db):
        """Should return None when no matching job exists."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        manager.create_job(
            "configs/test.json",
            ["2025-01-16"],
            ["gpt-5"]
        )

        found = manager.find_job_by_date_range(["2025-01-20", "2025-01-21"])
        assert found is None


@pytest.mark.unit
class TestJobProgress:
    """Test job progress tracking."""

    def test_progress_all_pending(self, clean_db):
        """Should show 0 completed when all pending."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job(
            "configs/test.json",
            ["2025-01-16", "2025-01-17"],
            ["gpt-5"]
        )

        progress = manager.get_job_progress(job_id)
        assert progress["total_model_days"] == 2
        assert progress["completed"] == 0
        assert progress["failed"] == 0
        assert progress["current"] is None

    def test_progress_with_running(self, clean_db):
        """Should identify currently running model-day."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job(
            "configs/test.json",
            ["2025-01-16"],
            ["gpt-5"]
        )

        manager.update_job_detail_status(job_id, "2025-01-16", "gpt-5", "running")

        progress = manager.get_job_progress(job_id)
        assert progress["current"] == {"date": "2025-01-16", "model": "gpt-5"}

    def test_progress_details(self, clean_db):
        """Should return detailed progress for all model-days."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job(
            "configs/test.json",
            ["2025-01-16"],
            ["gpt-5", "claude-3.7-sonnet"]
        )

        manager.update_job_detail_status(job_id, "2025-01-16", "gpt-5", "completed")

        progress = manager.get_job_progress(job_id)
        assert len(progress["details"]) == 2

        # Find the gpt-5 detail (order may vary)
        gpt5_detail = next(d for d in progress["details"] if d["model"] == "gpt-5")
        assert gpt5_detail["status"] == "completed"


@pytest.mark.unit
class TestConcurrencyControl:
    """Test concurrency control mechanisms."""

    def test_can_start_new_job_when_empty(self, clean_db):
        """Should allow job when none exist."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        assert manager.can_start_new_job() is True

    def test_can_start_new_job_blocks_pending(self, clean_db):
        """Should block when job is pending."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        manager.create_job("configs/test.json", ["2025-01-16"], ["gpt-5"])

        assert manager.can_start_new_job() is False

    def test_can_start_new_job_blocks_running(self, clean_db):
        """Should block when job is running."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job("configs/test.json", ["2025-01-16"], ["gpt-5"])
        manager.update_job_status(job_id, "running")

        assert manager.can_start_new_job() is False

    def test_can_start_new_job_allows_after_completion(self, clean_db):
        """Should allow new job after previous completes."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job("configs/test.json", ["2025-01-16"], ["gpt-5"])
        manager.update_job_status(job_id, "completed")

        assert manager.can_start_new_job() is True

    def test_get_running_jobs(self, clean_db):
        """Should return all running/pending jobs."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job1_id = manager.create_job("configs/test.json", ["2025-01-16"], ["gpt-5"])

        # Complete first job
        manager.update_job_status(job1_id, "completed")

        # Create second job
        job2_id = manager.create_job("configs/test.json", ["2025-01-17"], ["gpt-5"])

        running = manager.get_running_jobs()
        assert len(running) == 1
        assert running[0]["job_id"] == job2_id


@pytest.mark.unit
class TestJobCleanup:
    """Test maintenance operations."""

    def test_cleanup_old_jobs(self, clean_db):
        """Should delete jobs older than threshold."""
        from api.job_manager import JobManager
        from api.database import get_db_connection

        manager = JobManager(db_path=clean_db)

        # Create old job (manually set created_at)
        conn = get_db_connection(clean_db)
        cursor = conn.cursor()

        old_date = (datetime.utcnow() - timedelta(days=35)).isoformat() + "Z"
        cursor.execute("""
            INSERT INTO jobs (job_id, config_path, status, date_range, models, created_at)
            VALUES (?, ?, ?, ?, ?, ?)
        """, ("old-job", "configs/test.json", "completed", '["2025-01-01"]', '["gpt-5"]', old_date))
        conn.commit()
        conn.close()

        # Create recent job
        recent_id = manager.create_job("configs/test.json", ["2025-01-16"], ["gpt-5"])

        # Cleanup jobs older than 30 days
        result = manager.cleanup_old_jobs(days=30)

        assert result["jobs_deleted"] == 1
        assert manager.get_job("old-job") is None
        assert manager.get_job(recent_id) is not None


@pytest.mark.unit
class TestJobUpdateOperations:
    """Test job update methods."""

    def test_update_job_status_with_error(self, clean_db):
        """Should record error message when job fails."""
        from api.job_manager import JobManager

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job("configs/test.json", ["2025-01-16"], ["gpt-5"])

        manager.update_job_status(job_id, "failed", error="MCP service unavailable")

        job = manager.get_job(job_id)
        assert job["status"] == "failed"
        assert job["error"] == "MCP service unavailable"

    def test_update_job_detail_records_duration(self, clean_db):
        """Should calculate duration for completed model-days."""
        from api.job_manager import JobManager
        import time

        manager = JobManager(db_path=clean_db)
        job_id = manager.create_job("configs/test.json", ["2025-01-16"], ["gpt-5"])

        # Start
        manager.update_job_detail_status(job_id, "2025-01-16", "gpt-5", "running")

        # Small delay
        time.sleep(0.1)

        # Complete
        manager.update_job_detail_status(job_id, "2025-01-16", "gpt-5", "completed")

        progress = manager.get_job_progress(job_id)
        detail = progress["details"][0]

        assert detail["duration_seconds"] is not None
        assert detail["duration_seconds"] > 0


# Coverage target: 95%+ for api/job_manager.py
