"""#1714：入队后 celery_task_id 持久化路径覆盖（routes / enqueue / celery_app / 仓储）。

CI 无 redis、不走完整 HTTP 流程，这些 try/except 与早退分支此前覆盖率为 0。
用真实 SQLite 仓储 + monkeypatch celery_app.send_task 直接驱动路由函数：
- routes/ingest_jobs.submit_ingest_job：正常持久化 + 持久化异常吞掉不影响响应
- routes/task_center.retry_project_task（ingest 分支）：重试后持久化 + 异常吞掉
- routes/upload._persist_celery_task_id：空 id 早退 + 异常吞掉
- core/task_enqueue.safe_enqueue_generation_task：持久化失败仅 warning，入队仍 True
- core/celery_app：apply_queue_settings 抛异常时 API 启动不炸
- adapters/ingest_job_repository.update：写 celery_task_id 分支落库
"""

from __future__ import annotations

import importlib
import importlib.util
import os
import sys
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import MagicMock

os.environ.setdefault("JWT_SECRET_KEY", "unit-test-secret-key-for-testing")
os.environ.setdefault("DATABASE_URL", "sqlite:///test.db")

API_PATH = str(Path(__file__).resolve().parents[2] / "apps" / "api")
if API_PATH not in sys.path:
    sys.path.insert(0, API_PATH)

import pytest  # noqa: E402
from app.api.routes import ingest_jobs as ingest_jobs_route  # noqa: E402
from app.api.routes import task_center as task_center_route  # noqa: E402
from app.api.routes import upload as upload_route  # noqa: E402
from app.schemas.ingest_job import SubmitIngestJobRequest  # noqa: E402
from sqlalchemy import create_engine  # noqa: E402
from sqlalchemy.orm import sessionmaker  # noqa: E402

from packages.adapters.sqlalchemy_impl.ingest_job_repository import (  # noqa: E402
    SQLAlchemyIngestJobRepository,
)
from packages.adapters.sqlalchemy_impl.models import Base  # noqa: E402
from packages.domain import IngestJob, IngestJobStatus  # noqa: E402


def _ingest_repo():
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    session = sessionmaker(bind=engine)()
    return SQLAlchemyIngestJobRepository(session), session


def _fake_celery_result(task_id: str = "celery-route-msg-1"):
    result = MagicMock()
    result.id = task_id
    return result


# ── routes/ingest_jobs.submit_ingest_job ────────────────────────────────


def test_submit_ingest_job_persists_celery_task_id(monkeypatch):
    repo, session = _ingest_repo()
    monkeypatch.setattr(ingest_jobs_route.celery_app, "send_task", MagicMock(return_value=_fake_celery_result()))

    request = SubmitIngestJobRequest(project_id="proj-1", library_id="lib-1", storage_key="uploads/x.mov")
    response = ingest_jobs_route.submit_ingest_job(request, ingest_job_repository=repo)

    assert response.status == "pending"
    saved = repo.get(response.id)
    assert saved.celery_task_id == "celery-route-msg-1"


def test_submit_ingest_job_persist_failure_swallowed(monkeypatch):
    repo, _ = _ingest_repo()

    class _BoomRepo:
        def __init__(self, inner):
            self.inner = inner

        def create(self, job):
            return self.inner.create(job)

        def get(self, job_id):
            return self.inner.get(job_id)

        def update(self, job):  # noqa: ARG002
            raise RuntimeError("db write fail")

    boom_repo = _BoomRepo(repo)
    monkeypatch.setattr(ingest_jobs_route.celery_app, "send_task", MagicMock(return_value=_fake_celery_result()))

    request = SubmitIngestJobRequest(project_id="proj-1", library_id="lib-1", storage_key="uploads/y.mov")
    # 持久化异常被吞掉，主流程（响应）不受影响
    response = ingest_jobs_route.submit_ingest_job(request, ingest_job_repository=boom_repo)
    assert response.id
    assert response.status == "pending"


# ── routes/task_center.retry_project_task（ingest 分支） ────────────────


def _auth_user():
    user = SimpleNamespace(id="user-1")
    return SimpleNamespace(user=user, session_id=None, token_type=None)


def test_retry_ingest_job_persists_celery_task_id(monkeypatch):
    repo, session = _ingest_repo()
    # 造一条 failed 的 ingest job
    job = IngestJob.create(project_id="proj-1", library_id="lib-1", storage_key="uploads/z.mov")
    job.status = IngestJobStatus.FAILED
    repo.create(job)

    monkeypatch.setattr(
        task_center_route.celery_app, "send_task", MagicMock(return_value=_fake_celery_result("celery-retry-1"))
    )

    response = task_center_route.retry_project_task(
        "ingest", job.id, authenticated_user=_auth_user(), ingest_job_repository=repo
    )
    assert response.task_type == "ingest"
    new_id = response.id.split("ingest:")[1]
    retried = repo.get(new_id)
    assert retried is not None
    assert retried.celery_task_id == "celery-retry-1"


def test_retry_ingest_job_persist_failure_swallowed(monkeypatch):
    repo, _ = _ingest_repo()
    job = IngestJob.create(project_id="proj-1", library_id="lib-1", storage_key="uploads/w.mov")
    job.status = IngestJobStatus.FAILED
    repo.create(job)

    real_update = repo.update

    def _update_that_booms(entity):
        # 仅在写 celery_task_id 的那次 update 抛错（新建 job 后路由内的持久化）
        if getattr(entity, "celery_task_id", ""):
            raise RuntimeError("db write fail")
        return real_update(entity)

    repo.update = _update_that_booms  # type: ignore[method-assign]
    monkeypatch.setattr(task_center_route.celery_app, "send_task", MagicMock(return_value=_fake_celery_result()))

    # 持久化异常吞掉，重试接口仍正常返回
    response = task_center_route.retry_project_task(
        "ingest", job.id, authenticated_user=_auth_user(), ingest_job_repository=repo
    )
    assert response.task_type == "ingest"


# ── routes/upload._persist_celery_task_id ───────────────────────────────


def test_upload_persist_helper_empty_id_early_return():
    repo = MagicMock()
    job = MagicMock()
    upload_route._persist_celery_task_id(repo, job, "")
    repo.update.assert_not_called()
    upload_route._persist_celery_task_id(repo, job, None)  # type: ignore[arg-type]
    repo.update.assert_not_called()


def test_upload_persist_helper_exception_swallowed():
    repo = MagicMock()
    repo.update.side_effect = RuntimeError("db fail")
    job = MagicMock()
    # 不抛异常
    upload_route._persist_celery_task_id(repo, job, "celery-upload-1")
    repo.update.assert_called_once()
    assert job.celery_task_id == "celery-upload-1"


# ── core/task_enqueue：持久化失败仅 warning ─────────────────────────────


def test_safe_enqueue_persist_failure_still_returns_true(monkeypatch):
    from app.core import task_enqueue

    class _FakeTask:
        def __init__(self):
            self.id = "task-enqueue-persist-fail"
            self.status = "pending"
            self.celery_task_id = ""

        def mark_failed(self, msg):  # noqa: ARG002
            self.status = "failed"

    class _FakeRepo:
        def count_pending_total(self):
            return 0

        def count_pending_by_user(self, user_id):  # noqa: ARG002
            return 0

        def update(self, task):  # noqa: ARG002
            raise RuntimeError("persist celery_task_id failed")

    fake_result = MagicMock()
    fake_result.id = "celery-enqueue-fail-1"
    mock_celery = MagicMock()
    mock_celery.send_task.return_value = fake_result
    monkeypatch.setattr(task_enqueue, "celery_app", mock_celery)

    task = _FakeTask()
    repo = _FakeRepo()
    ok = task_enqueue.safe_enqueue_generation_task(task, repo, user_id="u1")
    # 持久化失败不影响入队结果
    assert ok is True
    mock_celery.send_task.assert_called_once()


# ── core/celery_app：队列配置失败不阻断 API 启动 ────────────────────────


def test_api_celery_app_survives_queue_settings_failure(monkeypatch):
    """apply_queue_settings 抛异常时 API 启动不炸（core/celery_app.py 的 try/except 分支）。

    通过让 `from packages.shared.celery_queues import apply_queue_settings` 本身
    抛异常来触发 except 分支；用全新模块名 reload，不替换已被其他模块持有的
    app.core.celery_app 模块对象，避免污染 task_enqueue 等导入方。
    """
    import builtins

    real_import = builtins.__import__

    def _failing_import(name, globals=None, locals=None, fromlist=(), level=0):  # noqa: A002
        if name == "packages.shared.celery_queues" and "apply_queue_settings" in (fromlist or ()):
            raise RuntimeError("config boom")
        return real_import(name, globals, locals, fromlist, level)

    monkeypatch.setattr(builtins, "__import__", _failing_import)

    spec = importlib.util.find_spec("app.core.celery_app")
    fresh_mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(fresh_mod)  # 异常在模块内被 try/except 吞掉
    assert fresh_mod.celery_app is not None
    assert fresh_mod.celery_app.main == "xiaoxia-saas-api"

    # 已加载的原模块对象不受影响（无 reload 污染）
    import app.core.celery_app as api_celery_mod

    assert api_celery_mod.celery_app is not None


# ── 仓储：update 写 celery_task_id 落库 ─────────────────────────────────


def test_ingest_repo_update_persists_celery_task_id():
    repo, session = _ingest_repo()
    job = IngestJob.create(project_id="proj-1", library_id="lib-1", storage_key="uploads/repo.mov")
    repo.create(job)

    job.celery_task_id = "celery-repo-update-1"
    repo.update(job)

    session.expire_all()
    saved = repo.get(job.id)
    assert saved.celery_task_id == "celery-repo-update-1"
