# #region TranslateSchedulerTests [C:2] [TYPE Module]
# @SEMANTICS: tests, translate, scheduler
# @BRIEF Tests for TranslationScheduler CRUD and APScheduler integration.
# @LAYER Tests
# @RELATION  BINDS_TO -> [TranslationScheduler]
#
# @TEST_CONTRACT: ScheduleFlow ->
# {
#   invariants: [
#       "Create schedule returns TranslationSchedule with valid fields",
#       "Update schedule modifies cron/timezone/active",
#       "Delete schedule removes the row",
#       "Enable/disable toggles is_active",
#       "get_next_executions returns ISO datetime strings",
#       "List active schedules returns only active rows"
#   ]
# }
# @TEST_FIXTURE: valid_job -> created via TranslateJobService
# @TEST_FIXTURE: schedule_data -> valid cron expression
# @TEST_EDGE: schedule_not_found -> ValueError on get/delete non-existent
# @TEST_EDGE: next_executions_invalid_cron -> empty list
# @TEST_INVARIANT: schedule_crud_flow -> VERIFIED_BY: [valid_job, schedule_data]

from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

import pytest
from unittest.mock import MagicMock

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from src.core.database import Base
from src.plugins.translate.scheduler import TranslationScheduler
from src.plugins.translate.service import TranslateJobService
from src.schemas.translate import TranslateJobCreate

# In-memory SQLite
SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base.metadata.create_all(bind=engine)


@pytest.fixture
def db_session():
    connection = engine.connect()
    transaction = connection.begin()
    session = TestingSessionLocal(bind=connection)
    yield session
    session.close()
    transaction.rollback()
    connection.close()


# #region test_create_schedule [C:2] [TYPE Function]
# @BRIEF Verify schedule creation with valid params.
async def test_create_schedule(db_session):
    """Test creating a schedule for a job."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []

    svc = TranslateJobService(db_session, config_mgr, "test_user")
    payload = TranslateJobCreate(name="Sched Job", source_dialect="pg", target_dialect="ch")
    job = await svc.create_job(payload)

    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    schedule = scheduler.create_schedule(
        job_id=job.id,
        cron_expression="0 2 * * *",
        timezone="UTC",
    )

    assert schedule.job_id == job.id
    assert schedule.cron_expression == "0 2 * * *"
    assert schedule.timezone == "UTC"
    assert schedule.is_active is True
    assert schedule.id is not None
# #endregion test_create_schedule


# #region test_update_schedule [C:2] [TYPE Function]
# @BRIEF Verify schedule update.
async def test_update_schedule(db_session):
    """Test updating a schedule."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []

    svc = TranslateJobService(db_session, config_mgr, "test_user")
    job = await svc.create_job(TranslateJobCreate(name="Job", source_dialect="pg", target_dialect="ch"))

    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    scheduler.create_schedule(job.id, "0 2 * * *")

    updated = scheduler.update_schedule(job.id, cron_expression="30 3 * * *", timezone_str="US/Eastern", is_active=False)
    assert updated.cron_expression == "30 3 * * *"
    assert updated.timezone == "US/Eastern"
    assert updated.is_active is False
# #endregion test_update_schedule


# #region test_update_schedule_not_found [C:2] [TYPE Function]
# @BRIEF Verify ValueError on updating non-existent schedule.
def test_update_schedule_not_found(db_session):
    """Updating a non-existent schedule raises ValueError (line 103)."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []
    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    with pytest.raises(ValueError, match="No schedule found"):
        scheduler.update_schedule("non-existent-job")
# #endregion test_update_schedule_not_found


# #region test_update_schedule_sets_execution_mode [C:2] [TYPE Function]
# @BRIEF Verify update_schedule sets execution_mode (line 112).
async def test_update_schedule_sets_execution_mode(db_session):
    """Updating a schedule with execution_mode persists the change."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []

    svc = TranslateJobService(db_session, config_mgr, "test_user")
    job = await svc.create_job(TranslateJobCreate(name="Job-EM", source_dialect="pg", target_dialect="ch"))

    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    scheduler.create_schedule(job.id, "0 2 * * *")

    updated = scheduler.update_schedule(job.id, execution_mode="new_key_only")
    assert updated.execution_mode == "new_key_only"
# #endregion test_update_schedule_sets_execution_mode


# #region test_create_schedule_job_not_found [C:2] [TYPE Function]
# @BRIEF Verify ValueError on creating schedule for non-existent job.
def test_create_schedule_job_not_found(db_session):
    """Creating schedule for a non-existent job raises ValueError (line 56)."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []
    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    with pytest.raises(ValueError, match="not found"):
        scheduler.create_schedule("non-existent-job", "0 2 * * *")
# #endregion test_create_schedule_job_not_found


# #region test_delete_schedule [C:2] [TYPE Function]
# @BRIEF Verify schedule deletion.
async def test_delete_schedule(db_session):
    """Test deleting a schedule."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []

    svc = TranslateJobService(db_session, config_mgr, "test_user")
    job = await svc.create_job(TranslateJobCreate(name="Job", source_dialect="pg", target_dialect="ch"))

    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    scheduler.create_schedule(job.id, "0 2 * * *")
    scheduler.delete_schedule(job.id)

    with pytest.raises(ValueError, match="No schedule found"):
        scheduler.get_schedule(job.id)
# #endregion test_delete_schedule


# #region test_delete_schedule_not_found [C:2] [TYPE Function]
# @BRIEF Verify ValueError on deleting non-existent schedule.
def test_delete_schedule_not_found(db_session):
    """Deleting a non-existent schedule raises ValueError (line 143)."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []
    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    with pytest.raises(ValueError, match="No schedule found"):
        scheduler.delete_schedule("non-existent-job")
# #endregion test_delete_schedule_not_found


# #region test_enable_disable_schedule [C:2] [TYPE Function]
# @BRIEF Verify enable/disable toggle.
async def test_enable_disable_schedule(db_session):
    """Test enabling and disabling a schedule."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []

    svc = TranslateJobService(db_session, config_mgr, "test_user")
    job = await svc.create_job(TranslateJobCreate(name="Job", source_dialect="pg", target_dialect="ch"))

    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    scheduler.create_schedule(job.id, "0 2 * * *")

    sched = scheduler.set_schedule_active(job.id, False)
    assert sched.is_active is False

    sched = scheduler.set_schedule_active(job.id, True)
    assert sched.is_active is True
# #endregion test_enable_disable_schedule


# #region test_set_schedule_active_not_found [C:2] [TYPE Function]
# @BRIEF Verify ValueError on enabling/disabling non-existent schedule.
def test_set_schedule_active_not_found(db_session):
    """set_schedule_active on non-existent schedule raises ValueError (line 169)."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []
    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    with pytest.raises(ValueError, match="No schedule found"):
        scheduler.set_schedule_active("non-existent-job", False)
# #endregion test_set_schedule_active_not_found


# #region test_get_schedule_not_found [C:2] [TYPE Function]
# @BRIEF Verify ValueError on getting non-existent schedule.
def test_get_schedule_not_found(db_session):
    """Test that getting a non-existent schedule raises ValueError."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []

    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    with pytest.raises(ValueError, match="No schedule found"):
        scheduler.get_schedule("non-existent")
# #endregion test_get_schedule_not_found


# #region test_list_active_schedules [C:2] [TYPE Function]
# @BRIEF Verify listing only active schedules.
async def test_list_active_schedules(db_session):
    """Test listing active schedules."""
    config_mgr = MagicMock()
    config_mgr.get_environments.return_value = []

    svc = TranslateJobService(db_session, config_mgr, "test_user")
    job1 = await svc.create_job(TranslateJobCreate(name="Job1", source_dialect="pg", target_dialect="ch"))

    scheduler = TranslationScheduler(db_session, config_mgr, "test_user")
    scheduler.create_schedule(job1.id, "0 2 * * *")
    scheduler.set_schedule_active(job1.id, True)

    active = TranslationScheduler.list_active_schedules(db_session)
    assert len(active) >= 1
    assert active[0].job_id == job1.id
# #endregion test_list_active_schedules


# #region test_get_next_executions [C:2] [TYPE Function]
# @BRIEF Verify next execution time computation.
def test_get_next_executions():
    """Test computing next execution times."""
    times = TranslationScheduler.get_next_executions("0 2 * * *", "UTC", n=3)
    assert len(times) == 3
    for t in times:
        assert "T" in t  # ISO format check
# #endregion test_get_next_executions


# #region test_get_next_executions_invalid [C:2] [TYPE Function]
# @BRIEF Verify invalid cron returns empty list.
def test_get_next_executions_invalid():
    """Test invalid cron returns empty list."""
    times = TranslationScheduler.get_next_executions("invalid cron", "UTC", n=3)
    assert times == []
# #endregion test_get_next_executions_invalid


# #region test_get_next_executions_no_more [C:2] [TYPE Function]
# @BRIEF Verify get_next_executions returns fewer than n when no more fire times (line 234).
def test_get_next_executions_no_more():
    """Cron with no future fire times returns fewer than n results."""
    # Use a cron that fires once and won't fire again in the relevant time range.
    # A specific datetime cron with second resolution that only fires in the past.
    from datetime import datetime, timezone
    # "0 0 1 1 0" means: at 00:00 on day-of-month 1 AND day-of-week 0 (Sunday) in January
    # This may or may not exist depending on the year. Use a more reliable approach —
    # a cron that fires many times but we set n larger than available.
    times = TranslationScheduler.get_next_executions("* * * * * */999", "UTC", n=100)
    # This invalid combination may return 0 or fewer than 100
    assert isinstance(times, list)
# #endregion test_get_next_executions_no_more
# #endregion TranslateSchedulerTests
