import pytest

pytestmark = pytest.mark.integration


@pytest.mark.asyncio
async def test_get_import_settings_defaults(client):
    resp = await client.get("/api/settings/import")
    assert resp.status_code == 200
    body = await resp.get_json()
    # Defaults from the migration:
    assert body["min_width"] == 0
    assert body["min_height"] == 0
    assert body["skip_transparent"] is False
    assert body["transparency_threshold"] == 0.9


@pytest.mark.asyncio
async def test_patch_import_settings(client):
    resp = await client.patch(
        "/api/settings/import",
        json={"min_width": 500, "skip_transparent": True, "transparency_threshold": 0.7},
    )
    assert resp.status_code == 200
    body = await resp.get_json()
    assert body["min_width"] == 500
    assert body["skip_transparent"] is True
    assert body["transparency_threshold"] == 0.7


@pytest.mark.asyncio
async def test_translation_settings_defaults_and_patch(client):
    # #143: translation is OFF by default with NO default host — the operator
    # points it at their own Interpreter proxy and enables it.
    body = await (await client.get("/api/settings/import")).get_json()
    assert body["translation_enabled"] is False
    assert body["interpreter_base_url"] == ""
    assert body["translation_target_lang"] == "en"

    ok = await client.patch("/api/settings/import", json={
        "translation_enabled": True,
        "interpreter_base_url": "http://interpreter.lan",
    })
    assert ok.status_code == 200
    out = await ok.get_json()
    assert out["translation_enabled"] is True
    assert out["interpreter_base_url"] == "http://interpreter.lan"

    # Type validation.
    assert (await client.patch(
        "/api/settings/import", json={"translation_enabled": "yes"})).status_code == 400
    assert (await client.patch(
        "/api/settings/import", json={"interpreter_base_url": 123})).status_code == 400


@pytest.mark.asyncio
async def test_translation_min_confidence_default_and_patch(client):
    # #155: the latin-script acceptance floor is an operator-tunable setting,
    # default 0.9 (stricter than the old hardcoded 0.8).
    body = await (await client.get("/api/settings/import")).get_json()
    assert body["translation_min_confidence"] == 0.9

    ok = await client.patch(
        "/api/settings/import", json={"translation_min_confidence": 0.95}
    )
    assert ok.status_code == 200
    assert (await ok.get_json())["translation_min_confidence"] == 0.95

    # Out-of-range + wrong-type are rejected.
    assert (await client.patch(
        "/api/settings/import", json={"translation_min_confidence": 1.5})).status_code == 400
    assert (await client.patch(
        "/api/settings/import", json={"translation_min_confidence": "high"})).status_code == 400


@pytest.mark.asyncio
async def test_translation_status_defaults(client):
    # Off + no URL → no network health call, healthy False (#143).
    body = await (await client.get("/api/settings/translation/status")).get_json()
    assert body["enabled"] is False
    assert body["base_url_set"] is False
    assert body["healthy"] is False
    assert "untranslated_count" in body
    assert body["active"] is False      # no sweep running
    assert body["last_run"] is None


@pytest.mark.asyncio
async def test_translation_status_reports_active_and_last_run(client, db):
    # A running translate/retranslate TaskRun → active True; the most recent
    # finished one → last_run (task basename + status).
    from datetime import UTC, datetime

    from backend.app.models import TaskRun

    db.add(TaskRun(
        celery_task_id="r-run", queue="maintenance_long",
        task_name="backend.app.tasks.translation.retranslate_posts",
        started_at=datetime.now(UTC), status="running",
    ))
    db.add(TaskRun(
        celery_task_id="r-done", queue="maintenance_long",
        task_name="backend.app.tasks.translation.translate_posts",
        started_at=datetime.now(UTC), finished_at=datetime.now(UTC),
        status="success",
    ))
    await db.commit()
    body = await (await client.get("/api/settings/translation/status")).get_json()
    assert body["active"] is True
    assert body["last_run"]["task"] == "translate_posts"
    assert body["last_run"]["status"] == "success"


@pytest.mark.asyncio
async def test_translation_run_requires_config(client):
    resp = await client.post("/api/settings/translation/run")
    assert resp.status_code == 400   # disabled / no base URL


@pytest.mark.asyncio
async def test_translation_test_connection(client, monkeypatch):
    # Tests a GIVEN url (not the saved one) without persisting anything.
    monkeypatch.setattr("backend.app.api.settings.ic.health", lambda *a, **k: True)
    resp = await client.post(
        "/api/settings/translation/test", json={"base_url": "http://i.lan"})
    assert resp.status_code == 200
    assert (await resp.get_json())["healthy"] is True
    # Empty URL → False, no health call.
    empty = await client.post(
        "/api/settings/translation/test", json={"base_url": ""})
    assert (await empty.get_json())["healthy"] is False


@pytest.mark.asyncio
async def test_translation_run_enqueues_when_configured(client, monkeypatch):
    sink = {}
    monkeypatch.setattr(
        "backend.app.tasks.translation.translate_posts.delay",
        lambda *a, **k: sink.update(k) or type("R", (), {"id": "t1"})(),
    )
    await client.patch("/api/settings/import", json={
        "translation_enabled": True, "interpreter_base_url": "http://i.lan",
    })
    resp = await client.post("/api/settings/translation/run")
    assert resp.status_code == 202
    assert (await resp.get_json())["celery_task_id"] == "t1"
    assert sink["drain"] is True   # "Translate now" chases the whole backlog


@pytest.mark.asyncio
async def test_translation_probe_requires_text(client):
    resp = await client.post(
        "/api/settings/translation/probe", json={"text": "  "})
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_translation_probe_requires_base_url(client):
    # Text given but no Interpreter URL saved → nothing to probe against.
    resp = await client.post(
        "/api/settings/translation/probe", json={"text": "hello"})
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_translation_probe_returns_detection(client, monkeypatch):
    # The diagnostic surfaces detected language + confidence + result, unsaved.
    monkeypatch.setattr(
        "backend.app.api.settings.ic.translate",
        lambda texts, **k: {
            "translations": ["Work in Progress"],
            "detected_lang": "de", "detected_confidence": 71.5,
            "engine": "llm", "engine_version": "v1",
        },
    )
    await client.patch("/api/settings/import", json={
        "translation_enabled": True, "interpreter_base_url": "http://i.lan",
    })
    resp = await client.post(
        "/api/settings/translation/probe", json={"text": "WIP"})
    assert resp.status_code == 200
    body = await resp.get_json()
    assert body["detected_lang"] == "de"
    assert body["detected_confidence"] == 71.5
    assert body["translated"] == "Work in Progress"
    assert body["target"] == "en"


def _fake_retranslate(monkeypatch, sink):
    # Record the kwargs the endpoint enqueues with, return a fake AsyncResult.
    monkeypatch.setattr(
        "backend.app.tasks.translation.retranslate_posts.delay",
        lambda *a, **k: sink.update(k) or type("R", (), {"id": "r1"})(),
    )


@pytest.mark.asyncio
async def test_translation_retranslate_requires_config(client):
    # Disabled → 400 even with an explicit scope (no reset is enqueued).
    resp = await client.post(
        "/api/settings/translation/retranslate", json={"all": True})
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_translation_retranslate_requires_target(client, monkeypatch):
    # 'all' must be explicit: an empty body is a 400, so a stray call can't
    # wipe every translation.
    _fake_retranslate(monkeypatch, {})
    await client.patch("/api/settings/import", json={
        "translation_enabled": True, "interpreter_base_url": "http://i.lan",
    })
    resp = await client.post("/api/settings/translation/retranslate")
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_translation_retranslate_artist_scope(client, monkeypatch):
    sink = {}
    _fake_retranslate(monkeypatch, sink)
    await client.patch("/api/settings/import", json={
        "translation_enabled": True, "interpreter_base_url": "http://i.lan",
    })
    resp = await client.post(
        "/api/settings/translation/retranslate", json={"artist_id": 7})
    assert resp.status_code == 202
    assert (await resp.get_json())["celery_task_id"] == "r1"
    assert sink["artist_ids"] == [7]


@pytest.mark.asyncio
async def test_translation_retranslate_all_scope(client, monkeypatch):
    sink = {}
    _fake_retranslate(monkeypatch, sink)
    await client.patch("/api/settings/import", json={
        "translation_enabled": True, "interpreter_base_url": "http://i.lan",
    })
    resp = await client.post(
        "/api/settings/translation/retranslate", json={"all": True})
    assert resp.status_code == 202
    assert sink["artist_ids"] is None


@pytest.mark.asyncio
async def test_translation_retranslate_bad_artist_id(client):
    resp = await client.post(
        "/api/settings/translation/retranslate", json={"artist_id": "abc"})
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_patch_rejects_non_object(client):
    resp = await client.patch("/api/settings/import", json=[1, 2, 3])
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_import_settings_phash_threshold_default(client):
    resp = await client.get("/api/settings/import")
    assert resp.status_code == 200
    assert (await resp.get_json())["phash_threshold"] == 10


@pytest.mark.asyncio
async def test_patch_phash_threshold(client):
    resp = await client.patch(
        "/api/settings/import", json={"phash_threshold": 4}
    )
    assert resp.status_code == 200
    assert (await resp.get_json())["phash_threshold"] == 4


@pytest.mark.asyncio
async def test_patch_phash_threshold_rejects_negative(client):
    resp = await client.patch(
        "/api/settings/import", json={"phash_threshold": -1}
    )
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_system_stats_shape(client):
    resp = await client.get("/api/system/stats")
    assert resp.status_code == 200
    body = await resp.get_json()
    for key in ("total_images", "total_tags", "storage_bytes", "subscription_count", "tasks", "active_batch"):
        assert key in body
    for status in ("pending", "queued", "processing", "complete", "skipped", "failed"):
        assert status in body["tasks"]


# --- FC-3d: scheduling knobs ----------------------------------------------


@pytest.mark.asyncio
async def test_get_includes_scheduling_defaults(client):
    resp = await client.get("/api/settings/import")
    body = await resp.get_json()
    assert body["download_schedule_default_seconds"] == 28800
    assert body["download_event_retention_days"] == 90
    assert body["download_failure_warning_threshold"] == 5


@pytest.mark.asyncio
async def test_patch_scheduling_defaults(client):
    resp = await client.patch("/api/settings/import", json={
        "download_schedule_default_seconds": 7200,
        "download_event_retention_days": 30,
        "download_failure_warning_threshold": 3,
    })
    assert resp.status_code == 200
    body = await resp.get_json()
    assert body["download_schedule_default_seconds"] == 7200
    assert body["download_event_retention_days"] == 30
    assert body["download_failure_warning_threshold"] == 3


@pytest.mark.asyncio
async def test_patch_rejects_below_min_interval(client):
    resp = await client.patch(
        "/api/settings/import",
        json={"download_schedule_default_seconds": 30},
    )
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_patch_rejects_above_max_interval(client):
    resp = await client.patch(
        "/api/settings/import",
        json={"download_schedule_default_seconds": 90000},
    )
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_patch_rejects_zero_retention_days(client):
    resp = await client.patch(
        "/api/settings/import",
        json={"download_event_retention_days": 0},
    )
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_patch_rejects_zero_failure_threshold(client):
    resp = await client.patch(
        "/api/settings/import",
        json={"download_failure_warning_threshold": 0},
    )
    assert resp.status_code == 400


@pytest.mark.asyncio
async def test_system_stats_integrity_counts(client, db):
    from backend.app.models import ImageRecord

    base = "stats_" + "0" * 56
    for i, status in enumerate(
        ["ok", "ok", "corrupt", "failed_verification"]
    ):
        db.add(ImageRecord(
            path=f"/images/i/{i}.jpg", sha256=base + f"{i:02d}",
            size_bytes=1, mime="image/jpeg", width=1, height=1,
            origin="imported_filesystem", integrity_status=status,
        ))
    await db.commit()

    resp = await client.get("/api/system/stats")
    assert resp.status_code == 200
    body = await resp.get_json()
    integ = body["integrity"]
    assert integ["ok"] >= 2
    assert integ["corrupt"] >= 1
    assert integ["failed_verification"] >= 1
    assert "unknown" in integ
