"""SP25 — PATCH /api/clearhouse endpoint.

Lets the operator flip sftp_block.stub and adjust host/port/paths
without raw SQL. The endpoint validates via the Clearhouse Pydantic
model, writes via store.update_clearhouse(), then hot-reloads the
running scheduler via scheduler.reconfigure_scheduler().
"""
from __future__ import annotations

from unittest.mock import AsyncMock, patch

import pytest
from fastapi.testclient import TestClient

from cyclone import scheduler as sched_mod
from cyclone.api import app
from cyclone.auth import deps
from cyclone.store import store as cycl_store


@pytest.fixture
def client():
    return TestClient(app)


def _seed_clearhouse():
    """Insert the default clearhouse row used by SP9's lifespan seed."""
    cycl_store.ensure_clearhouse_seeded()


def _clearhouse_body_from_get(client, **overrides) -> dict:
    """GET the current clearhouse row and apply ``overrides`` to the
    sftp_block. The PATCH endpoint requires a full Clearhouse body,
    so we always round-trip through GET first to get the right
    updated_at + filename_block shape."""
    r = client.get("/api/clearhouse")
    assert r.status_code == 200, r.text
    body = r.json()
    sb = dict(body["sftp_block"])
    sb.update(overrides)
    body["sftp_block"] = sb
    return body


def _real_block_overrides() -> dict:
    """Default overrides for stub=False PATCHes — the auth shape must
    match what ``SftpClient._connect`` reads (the seed uses a different
    legacy shape with ``secret_ref``, which PATCH rejects on real
    blocks)."""
    return {
        "stub": False,
        "host": "mft.example.com",
        "auth": {"password_keychain_account": "sftp.gainwell.password"},
    }


def test_patch_flips_stub_and_get_reflects(client):
    _seed_clearhouse()
    overrides = _real_block_overrides()
    body = _clearhouse_body_from_get(client, **overrides)
    r = client.patch("/api/clearhouse", json=body)
    assert r.status_code == 200, r.text
    payload = r.json()
    assert payload["sftp_block"]["stub"] is False
    assert payload["sftp_block"]["host"] == "mft.example.com"

    r2 = client.get("/api/clearhouse")
    assert r2.status_code == 200
    assert r2.json()["sftp_block"]["stub"] is False


def test_patch_with_string_stub_returns_422(client):
    _seed_clearhouse()
    body = _clearhouse_body_from_get(client)
    body["sftp_block"]["stub"] = "yes"  # string instead of bool
    r = client.patch("/api/clearhouse", json=body)
    assert r.status_code == 422


def test_patch_real_block_requires_host(client):
    _seed_clearhouse()
    overrides = _real_block_overrides()
    overrides["host"] = ""  # override the real-block default
    body = _clearhouse_body_from_get(client, **overrides)
    r = client.patch("/api/clearhouse", json=body)
    assert r.status_code == 422


def test_patch_real_block_requires_password_keychain_account(client):
    _seed_clearhouse()
    overrides = _real_block_overrides()
    overrides["auth"] = {"password_keychain_account": ""}
    body = _clearhouse_body_from_get(client, **overrides)
    r = client.patch("/api/clearhouse", json=body)
    assert r.status_code == 422


def test_patch_without_session_returns_401(client):
    """With AUTH_DISABLED off (the production gate), a request with no
    session cookie must be rejected at the gate before reaching the
    endpoint body. Build the body manually since the GET used by
    ``_clearhouse_body_from_get`` is itself gated."""
    _seed_clearhouse()
    # Build a valid body without going through the gated GET.
    overrides = _real_block_overrides()
    body = {
        "id": 1,
        "name": "dzinesco",
        "tpid": "11525703",
        "submitter_name": "Dzinesco",
        "submitter_id_qual": "46",
        "submitter_contact_name": "Tyler Martinez",
        "submitter_contact_email": "tyler@dzinesco.com",
        "filename_block": {
            "tz": "America/Denver",
            "outbound_template": "{tpid}-{tx}-{ts}-1of1.{ext}",
            "inbound_template": "TP{tpid}-{orig_tx}_M{tracking}-{ts}-1of1_{file_type}.x12",
        },
        "sftp_block": {
            "host": overrides.get("host", "mft.example.com"),
            "port": 22,
            "username": "colorado-fts\\coxix_prod_11525703",
            "paths": {
                "outbound": "/CO XIX/PROD/coxix_prod_11525703/ToHPE",
                "inbound": "/CO XIX/PROD/coxix_prod_11525703/FromHPE",
            },
            "stub": overrides.get("stub", False),
            "staging_dir": "./var/sftp/staging",
            "auth": overrides.get("auth", {"password_keychain_account": "sftp.gainwell.password"}),
        },
        "updated_at": "2026-06-24T00:00:00+00:00",
    }

    deps.AUTH_DISABLED = False
    try:
        r = client.patch("/api/clearhouse", json=body)
        assert r.status_code == 401
    finally:
        deps.AUTH_DISABLED = True


def test_patch_triggers_scheduler_reconfigure(client):
    """The PATCH must call scheduler.reconfigure_scheduler() with the new
    SftpBlock so the next tick uses real MFT instead of the stub."""
    _seed_clearhouse()

    with patch.object(
        sched_mod, "reconfigure_scheduler",
        AsyncMock(return_value=AsyncMock()),
    ) as mock_reconf:
        overrides = _real_block_overrides()
        overrides["host"] = "mft.gainwelltechnologies.com"
        body = _clearhouse_body_from_get(client, **overrides)
        r = client.patch("/api/clearhouse", json=body)
        assert r.status_code == 200, r.text
        assert mock_reconf.await_count == 1
        # The new sftp_block is passed positionally; sftp_block_name
        # comes from the clearhouse row's ``name`` field.
        call_args = mock_reconf.await_args
        assert call_args.args[0].stub is False
        assert call_args.args[0].host == "mft.gainwelltechnologies.com"
        assert call_args.kwargs.get("sftp_block_name") == "dzinesco"


def test_patch_returns_post_update_row(client):
    _seed_clearhouse()
    body = _clearhouse_body_from_get(client, **_real_block_overrides())
    r = client.patch("/api/clearhouse", json=body)
    assert r.status_code == 200
    payload = r.json()
    assert payload["name"] == "dzinesco"
    assert payload["tpid"] == "11525703"
    assert payload["sftp_block"]["stub"] is False
    assert payload["sftp_block"]["host"] == "mft.example.com"
