# #region Test.Api.TranslateScheduleRoutes [C:3] [TYPE Module] [SEMANTICS test,translate,schedule,api]
# @BRIEF Tests for _schedule_routes.py — schedule CRUD, enable/disable, next executions.
# @RELATION BINDS_TO -> [TranslateScheduleRoutesModule]
# @TEST_EDGE: schedule_not_found -> 404
# @TEST_EDGE: schedule_set_value_error -> 400
# @TEST_EDGE: schedule_enable_not_found -> 404
# @TEST_EDGE: schedule_disable_not_found -> 404
# @TEST_EDGE: schedule_delete_not_found -> 404

import os
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-tests")

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

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

_src = str(Path(__file__).resolve().parent.parent.parent / "src")
if _src not in sys.path:
    sys.path.insert(0, _src)


def _make_client(overrides: dict | None = None) -> TestClient:
    from src.api.routes.translate._schedule_routes import router
    from src.dependencies import (
        get_config_manager, get_current_user, get_db,
        has_permission, require_feature,
    )
    from src.schemas.auth import User, RoleSchema

    app = FastAPI()
    app.include_router(router)

    mock_user = User(
        id="user-1", username="admin", email="admin@x.com",
        auth_source="LOCAL",
        created_at=__import__("datetime").datetime.now(),
        roles=[RoleSchema(id="r1", name="Admin", description="", permissions=[])],
    )

    defaults = {
        get_current_user: lambda: mock_user,
        get_config_manager: lambda: MagicMock(),
        get_db: lambda: MagicMock(),
        has_permission: lambda *a, **kw: lambda: None,
        require_feature: lambda *a, **kw: lambda: None,
    }
    if overrides:
        defaults.update(overrides)
    for dep, mock_fn in defaults.items():
        app.dependency_overrides[dep] = mock_fn
    return TestClient(app)


def _mock_schedule(
    id="sched-1", job_id="job-1",
    cron_expression="0 2 * * *", timezone="UTC",
    is_active=True, execution_mode="full",
    last_run_at=None, next_run_at=None,
    created_by="admin",
    created_at=None, updated_at=None,
):
    from datetime import datetime, UTC
    s = MagicMock()
    s.id = id
    s.job_id = job_id
    s.cron_expression = cron_expression
    s.timezone = timezone
    s.is_active = is_active
    s.execution_mode = execution_mode
    s.last_run_at = last_run_at or datetime(2026, 1, 1, 2, 0, 0, tzinfo=UTC)
    s.next_run_at = next_run_at or datetime(2026, 1, 2, 2, 0, 0, tzinfo=UTC)
    s.created_by = created_by
    s.created_at = created_at or datetime(2026, 1, 1, 0, 0, 0, tzinfo=UTC)
    s.updated_at = updated_at
    return s


# =============================================================================
# Get Schedule
# =============================================================================

class TestGetSchedule:
    """GET /api/translate/jobs/{job_id}/schedule"""

    def test_get_schedule_success(self):
        mock_scheduler = MagicMock()
        mock_scheduler.get_schedule.return_value = _mock_schedule()

        with patch(
            "src.api.routes.translate._schedule_routes.TranslationScheduler",
            return_value=mock_scheduler,
        ):
            client = _make_client()
            resp = client.get("/api/translate/jobs/job-1/schedule")
        assert resp.status_code == 200
        data = resp.json()
        assert data["id"] == "sched-1"
        assert data["cron_expression"] == "0 2 * * *"
        assert data["is_active"] is True

    def test_get_schedule_not_found_404(self):
        mock_scheduler = MagicMock()
        mock_scheduler.get_schedule.side_effect = ValueError("Schedule not found")

        with patch(
            "src.api.routes.translate._schedule_routes.TranslationScheduler",
            return_value=mock_scheduler,
        ):
            client = _make_client()
            resp = client.get("/api/translate/jobs/job-missing/schedule")
        assert resp.status_code == 404


# =============================================================================
# Set Schedule
# =============================================================================

class TestSetSchedule:
    """PUT /api/translate/jobs/{job_id}/schedule"""

    def test_set_schedule_create(self):
        mock_scheduler = MagicMock()
        mock_scheduler.create_schedule.return_value = _mock_schedule()

        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.return_value = None

        mock_sched_svc = MagicMock()

        from src.dependencies import get_db

        with (
            patch(
                "src.api.routes.translate._schedule_routes.TranslationScheduler",
                return_value=mock_scheduler,
            ),
            patch(
                "src.dependencies.get_scheduler_service",
                return_value=mock_sched_svc,
            ),
        ):
            client = _make_client({get_db: lambda: mock_db})
            resp = client.put("/api/translate/jobs/job-1/schedule", json={
                "cron_expression": "0 2 * * *",
                "timezone": "UTC",
                "is_active": True,
                "execution_mode": "full",
            })
        assert resp.status_code == 200
        data = resp.json()
        assert data["cron_expression"] == "0 2 * * *"
        mock_scheduler.create_schedule.assert_called_once()
        mock_sched_svc.add_translation_job.assert_called_once()

    def test_set_schedule_update(self):
        mock_scheduler = MagicMock()
        mock_scheduler.update_schedule.return_value = _mock_schedule()

        mock_existing = MagicMock()
        mock_existing.id = "sched-1"
        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.return_value = mock_existing

        mock_sched_svc = MagicMock()

        from src.dependencies import get_db

        with (
            patch(
                "src.api.routes.translate._schedule_routes.TranslationScheduler",
                return_value=mock_scheduler,
            ),
            patch(
                "src.dependencies.get_scheduler_service",
                return_value=mock_sched_svc,
            ),
        ):
            client = _make_client({get_db: lambda: mock_db})
            resp = client.put("/api/translate/jobs/job-1/schedule", json={
                "cron_expression": "0 3 * * *",
                "timezone": "Europe/Moscow",
                "is_active": False,
                "execution_mode": "new_key_only",
            })
        assert resp.status_code == 200
        mock_scheduler.update_schedule.assert_called_once()
        mock_sched_svc.add_translation_job.assert_called_once()

    def test_set_schedule_value_error_400(self):
        mock_scheduler = MagicMock()
        mock_scheduler.create_schedule.side_effect = ValueError("Invalid cron")

        mock_db = MagicMock()
        mock_db.query.return_value.filter.return_value.first.return_value = None

        from src.dependencies import get_db

        with patch(
            "src.api.routes.translate._schedule_routes.TranslationScheduler",
            return_value=mock_scheduler,
        ):
            client = _make_client({get_db: lambda: mock_db})
            resp = client.put("/api/translate/jobs/job-1/schedule", json={
                "cron_expression": "bad cron",
                "timezone": "UTC",
            })
        assert resp.status_code == 400

    def test_set_schedule_422_missing_cron(self):
        client = _make_client()
        resp = client.put("/api/translate/jobs/job-1/schedule", json={
            "timezone": "UTC",
        })
        assert resp.status_code == 422
        assert "cron_expression" in resp.text


# =============================================================================
# Enable Schedule
# =============================================================================

class TestEnableSchedule:
    """POST /api/translate/jobs/{job_id}/schedule/enable"""

    def test_enable_success(self):
        mock_scheduler = MagicMock()
        mock_scheduler.set_schedule_active.return_value = _mock_schedule(is_active=True)
        mock_sched_svc = MagicMock()

        with (
            patch(
                "src.api.routes.translate._schedule_routes.TranslationScheduler",
                return_value=mock_scheduler,
            ),
            patch(
                "src.dependencies.get_scheduler_service",
                return_value=mock_sched_svc,
            ),
        ):
            client = _make_client()
            resp = client.post("/api/translate/jobs/job-1/schedule/enable")
        assert resp.status_code == 200
        data = resp.json()
        assert data["status"] == "enabled"
        assert data["job_id"] == "job-1"

    def test_enable_not_found_404(self):
        mock_scheduler = MagicMock()
        mock_scheduler.set_schedule_active.side_effect = ValueError("Schedule not found")

        with patch(
            "src.api.routes.translate._schedule_routes.TranslationScheduler",
            return_value=mock_scheduler,
        ):
            client = _make_client()
            resp = client.post("/api/translate/jobs/job-missing/schedule/enable")
        assert resp.status_code == 404


# =============================================================================
# Disable Schedule
# =============================================================================

class TestDisableSchedule:
    """POST /api/translate/jobs/{job_id}/schedule/disable"""

    def test_disable_success(self):
        mock_scheduler = MagicMock()
        mock_scheduler.set_schedule_active.return_value = _mock_schedule(is_active=False)
        mock_sched_svc = MagicMock()

        with (
            patch(
                "src.api.routes.translate._schedule_routes.TranslationScheduler",
                return_value=mock_scheduler,
            ),
            patch(
                "src.dependencies.get_scheduler_service",
                return_value=mock_sched_svc,
            ),
        ):
            client = _make_client()
            resp = client.post("/api/translate/jobs/job-1/schedule/disable")
        assert resp.status_code == 200
        data = resp.json()
        assert data["status"] == "disabled"

    def test_disable_not_found_404(self):
        mock_scheduler = MagicMock()
        mock_scheduler.set_schedule_active.side_effect = ValueError("Schedule not found")

        with patch(
            "src.api.routes.translate._schedule_routes.TranslationScheduler",
            return_value=mock_scheduler,
        ):
            client = _make_client()
            resp = client.post("/api/translate/jobs/job-missing/schedule/disable")
        assert resp.status_code == 404


# =============================================================================
# Delete Schedule
# =============================================================================

class TestDeleteSchedule:
    """DELETE /api/translate/jobs/{job_id}/schedule"""

    def test_delete_success(self):
        mock_scheduler = MagicMock()
        mock_scheduler.delete_schedule.return_value = None
        mock_sched_svc = MagicMock()

        with (
            patch(
                "src.api.routes.translate._schedule_routes.TranslationScheduler",
                return_value=mock_scheduler,
            ),
            patch(
                "src.dependencies.get_scheduler_service",
                return_value=mock_sched_svc,
            ),
        ):
            client = _make_client()
            resp = client.delete("/api/translate/jobs/job-1/schedule")
        assert resp.status_code == 204

    def test_delete_not_found_404(self):
        mock_scheduler = MagicMock()
        mock_scheduler.delete_schedule.side_effect = ValueError("Schedule not found")

        with patch(
            "src.api.routes.translate._schedule_routes.TranslationScheduler",
            return_value=mock_scheduler,
        ):
            client = _make_client()
            resp = client.delete("/api/translate/jobs/job-missing/schedule")
        assert resp.status_code == 404


# =============================================================================
# Get Next Executions
# =============================================================================

class TestGetNextExecutions:
    """GET /api/translate/jobs/{job_id}/schedule/next-executions"""

    def test_next_executions_success(self):
        sched = _mock_schedule(cron_expression="0 2 * * *", timezone="UTC")

        mock_scheduler = MagicMock()
        mock_scheduler.get_schedule.return_value = sched

        # TranslationScheduler is replaced by a MagicMock class.
        # `get_next_executions` is a static method called on the class itself, not on instance.
        # Set it on the mock class so TranslationScheduler.get_next_executions() works.
        mock_sched_class = MagicMock(return_value=mock_scheduler)
        mock_sched_class.get_next_executions.return_value = [
            "2026-01-02T02:00:00Z", "2026-01-03T02:00:00Z",
        ]

        with patch(
            "src.api.routes.translate._schedule_routes.TranslationScheduler",
            mock_sched_class,
        ):
            client = _make_client()
            resp = client.get("/api/translate/jobs/job-1/schedule/next-executions?n=2")
        assert resp.status_code == 200
        data = resp.json()
        assert data["cron_expression"] == "0 2 * * *"
        assert len(data["next_executions"]) == 2

    def test_next_executions_not_found_404(self):
        mock_scheduler = MagicMock()
        mock_scheduler.get_schedule.side_effect = ValueError("Schedule not found")

        with patch(
            "src.api.routes.translate._schedule_routes.TranslationScheduler",
            return_value=mock_scheduler,
        ):
            client = _make_client()
            resp = client.get("/api/translate/jobs/job-missing/schedule/next-executions")
        assert resp.status_code == 404
# #endregion Test.Api.TranslateScheduleRoutes
