from __future__ import annotations

from pathlib import Path

import yaml
from fastapi.testclient import TestClient


def _write_config(tmp_path: Path, *, enabled: bool) -> Path:
    cfg = {
        "ray": {
            "address": "http://127.0.0.1:8265",
            "shared_root": "/private",
            "entrypoint_resources": {"worker_node": 1},
            "runtime_env": {"env_vars": {}},
        },
        "data": {
            "user_root": str(tmp_path / "users"),
            "sftpgo": {
                "enabled": bool(enabled),
                "admin_api_base": "http://127.0.0.1:8081/api/v2",
                "admin_user": "admin",
                "admin_password_env": "SFTPGO_ADMIN_PASSWORD",
                "host": "h1.internal",
                "sftp_port": 2022,
            },
        },
        "service": {
            "api": {"host": "127.0.0.1", "port": 0},
            "auth": {"token_env": "MVP_INTERNAL_TOKEN"},
            "sqlite": {"db_path": str(tmp_path / "mvp.sqlite3")},
            "scheduler": {"tick_s": 1, "retry_interval_s": 1, "max_running_tasks": 1},
        },
    }
    p = tmp_path / "cfg.yaml"
    p.write_text(yaml.safe_dump(cfg), encoding="utf-8")
    return p


def test_create_user_calls_sftpgo_when_enabled(tmp_path: Path, monkeypatch):
    from argus.service import app as app_mod

    cfg_path = _write_config(tmp_path, enabled=True)
    monkeypatch.setenv("MVP_INTERNAL_TOKEN", "adm1")
    monkeypatch.setenv("SFTPGO_ADMIN_PASSWORD", "pw1")

    calls = {"create": [], "disable": [], "reset": []}

    class _Client:
        def __init__(self, **kwargs):
            pass

        def create_user(self, *, username: str, password: str, home_dir: str) -> None:
            calls["create"].append((username, password, home_dir))

        def enable_user(self, *, username: str, home_dir: str) -> None:
            # Not used in this test, but required by app for idempotent upsert.
            return None

        def disable_user(self, *, username: str, home_dir: str) -> None:
            calls["disable"].append(username)

        def reset_password(self, *, username: str, new_password: str, home_dir: str) -> None:
            calls["reset"].append((username, new_password))

    monkeypatch.setattr(app_mod, "SFTPGoAdminClient", _Client)

    class _Scheduler:
        def __init__(self, **kwargs):
            self.tool = object()

        def run_forever(self, stop_flag):
            return None

    monkeypatch.setattr(app_mod, "Scheduler", _Scheduler)
    app = app_mod.create_app(str(cfg_path))

    admin_headers = {"authorization": "Bearer adm1"}
    with TestClient(app) as c:
        r = c.post("/api/v2/users", headers=admin_headers, json={"user_id": "alice"})
        assert r.status_code == 200
        assert calls["create"]
        username, password, home_dir = calls["create"][-1]
        assert username == "alice"
        assert password
        assert home_dir.endswith("/users/alice")

        r2 = c.post("/api/v2/users/alice:disable", headers=admin_headers)
        assert r2.status_code == 200
        assert calls["disable"] == ["alice"]

        r3 = c.post("/api/v2/users/alice/sftp:reset_password", headers=admin_headers)
        assert r3.status_code == 200
        body = r3.json()
        assert body["user_id"] == "alice"
        assert body["password"]
        assert calls["reset"] and calls["reset"][-1][0] == "alice"


def test_create_user_upserts_when_sftpgo_user_already_exists(tmp_path: Path, monkeypatch):
    from argus.service import app as app_mod
    from argus.service.sftpgo import SFTPGoError

    cfg_path = _write_config(tmp_path, enabled=True)
    monkeypatch.setenv("MVP_INTERNAL_TOKEN", "adm1")
    monkeypatch.setenv("SFTPGO_ADMIN_PASSWORD", "pw1")

    calls = {"create": 0, "reset": [], "enable": []}

    class _Client:
        def __init__(self, **kwargs):
            pass

        def create_user(self, *, username: str, password: str, home_dir: str) -> None:
            calls["create"] += 1
            raise SFTPGoError("sftpgo http error: 409 Conflict")

        def reset_password(self, *, username: str, new_password: str, home_dir: str) -> None:
            calls["reset"].append((username, new_password))

        def enable_user(self, *, username: str, home_dir: str) -> None:
            calls["enable"].append(username)

        def disable_user(self, *, username: str, home_dir: str) -> None:
            return None

    monkeypatch.setattr(app_mod, "SFTPGoAdminClient", _Client)

    class _Scheduler:
        def __init__(self, **kwargs):
            self.tool = object()

        def run_forever(self, stop_flag):
            return None

    monkeypatch.setattr(app_mod, "Scheduler", _Scheduler)
    app = app_mod.create_app(str(cfg_path))

    admin_headers = {"authorization": "Bearer adm1"}
    with TestClient(app) as c:
        r = c.post("/api/v2/users", headers=admin_headers, json={"user_id": "bob"})
        assert r.status_code == 200
        body = r.json()
        assert body["user_id"] == "bob"
        assert body.get("sftp", {}).get("password")
        assert calls["create"] == 1
        assert calls["reset"] and calls["reset"][-1][0] == "bob"
        assert calls["enable"] == ["bob"]


def test_sftpgo_enabled_requires_admin_password_env(tmp_path: Path, monkeypatch):
    from argus.service import app as app_mod

    cfg_path = _write_config(tmp_path, enabled=True)
    monkeypatch.setenv("MVP_INTERNAL_TOKEN", "adm1")
    monkeypatch.delenv("SFTPGO_ADMIN_PASSWORD", raising=False)

    class _Scheduler:
        def __init__(self, **kwargs):
            self.tool = object()

        def run_forever(self, stop_flag):
            return None

    monkeypatch.setattr(app_mod, "Scheduler", _Scheduler)
    app = app_mod.create_app(str(cfg_path))

    admin_headers = {"authorization": "Bearer adm1"}
    with TestClient(app) as c:
        r = c.post("/api/v2/users", headers=admin_headers, json={"user_id": "alice"})
        assert r.status_code == 500
        assert "SFTPGO_ADMIN_PASSWORD" in r.text


def test_sftpgo_admin_client_builds_requests(monkeypatch):
    import json
    from urllib.request import Request

    from argus.service.sftpgo import SFTPGoAdminClient
    import argus.service.sftpgo as mod

    seen: list[Request] = []

    class _Resp:
        def __init__(self, body: bytes = b"ok"):
            self._body = body

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc, tb):
            return False

        def read(self):
            return self._body

    def fake_urlopen(req, timeout=0):
        seen.append(req)
        if req.full_url.endswith("/token"):
            return _Resp(body=b'{"access_token":"t1","expires_at":0}')
        # allow folder creates to be idempotent in tests
        if req.full_url.endswith("/folders") and req.get_method() == "POST":
            return _Resp(body=b'{"message":"created"}')
        if req.full_url.endswith("/users/alice") and req.get_method() == "GET":
            return _Resp(
                body=b'{"username":"alice","status":1,"home_dir":"/private/users/alice","uid":0,"gid":0,"permissions":{"/":["*"]},"virtual_folders":[]}'
            )
        return _Resp()

    monkeypatch.setattr(mod, "urlopen", fake_urlopen)

    c = SFTPGoAdminClient(admin_api_base="http://sftpgo.local/api/v2", admin_user="admin", admin_password="pw", common_root="/private/common")
    c.create_user(username="alice", password="p1", home_dir="/private/users/alice")
    c.disable_user(username="alice", home_dir="/private/users/alice")
    c.enable_user(username="alice", home_dir="/private/users/alice")
    c.reset_password(username="alice", new_password="p2", home_dir="/private/users/alice")

    # Each operation fetches a token, then issues a request.
    # For create_user: token + ensure folders (2 POSTs) + create user
    # For disable/enable/reset: token + ensure folders (2 POSTs) + GET user + PUT user
    assert len(seen) == 1 + 2 + 1 + 3 * (1 + 2 + 1 + 1)
    assert seen[0].full_url.endswith("/token")
    assert seen[0].headers.get("Authorization", "").startswith("Basic ")
    # create_user
    assert seen[1].full_url.endswith("/folders")
    assert seen[2].full_url.endswith("/folders")
    assert seen[3].full_url.endswith("/users")
    assert seen[3].headers.get("Authorization", "") == "Bearer t1"
    created = json.loads(seen[3].data.decode("utf-8"))
    assert created["username"] == "alice"
    assert "/common" in created.get("permissions", {})
    assert "/common/datasets" in created.get("permissions", {})
    assert "/common/hf" in created.get("permissions", {})

    # disable_user
    assert seen[4].full_url.endswith("/token")
    assert seen[5].full_url.endswith("/folders")
    assert seen[6].full_url.endswith("/folders")
    assert seen[7].full_url.endswith("/users/alice")
    assert seen[7].get_method() == "GET"
    assert seen[8].full_url.endswith("/users/alice")
    assert seen[8].get_method() == "PUT"

    # enable_user
    assert seen[9].full_url.endswith("/token")
    assert seen[10].full_url.endswith("/folders")
    assert seen[11].full_url.endswith("/folders")
    assert seen[12].full_url.endswith("/users/alice")
    assert seen[12].get_method() == "GET"
    assert seen[13].full_url.endswith("/users/alice")
    assert seen[13].get_method() == "PUT"

    # reset_password
    assert seen[14].full_url.endswith("/token")
    assert seen[15].full_url.endswith("/folders")
    assert seen[16].full_url.endswith("/folders")
    assert seen[17].full_url.endswith("/users/alice")
    assert seen[17].get_method() == "GET"
    assert seen[18].full_url.endswith("/users/alice")
    assert seen[18].get_method() == "PUT"


def test_sftpgo_admin_client_http_error_raises(monkeypatch):
    from urllib.error import HTTPError

    from argus.service.sftpgo import SFTPGoAdminClient, SFTPGoError
    import argus.service.sftpgo as mod

    def fake_urlopen(req, timeout=0):
        if req.full_url.endswith("/token"):
            class _Resp:
                def __enter__(self):
                    return self

                def __exit__(self, exc_type, exc, tb):
                    return False

                def read(self):
                    return b'{"access_token":"t1","expires_at":0}'

            return _Resp()
        raise HTTPError(req.full_url, 500, "boom", hdrs=None, fp=None)

    monkeypatch.setattr(mod, "urlopen", fake_urlopen)

    c = SFTPGoAdminClient(admin_api_base="http://sftpgo.local/api/v2", admin_user="admin", admin_password="pw", common_root="/private/common")
    try:
        c.create_user(username="alice", password="p1", home_dir="/private/users/alice")
        assert False, "expected SFTPGoError"
    except SFTPGoError as e:
        assert "http error" in str(e)


def test_sftpgo_admin_client_url_error_raises(monkeypatch):
    from urllib.error import URLError

    from argus.service.sftpgo import SFTPGoAdminClient, SFTPGoError
    import argus.service.sftpgo as mod

    def fake_urlopen(req, timeout=0):
        if req.full_url.endswith("/token"):
            class _Resp:
                def __enter__(self):
                    return self

                def __exit__(self, exc_type, exc, tb):
                    return False

                def read(self):
                    return b'{"access_token":"t1","expires_at":0}'

            return _Resp()
        raise URLError("no route")

    monkeypatch.setattr(mod, "urlopen", fake_urlopen)

    c = SFTPGoAdminClient(admin_api_base="http://sftpgo.local/api/v2", admin_user="admin", admin_password="pw")
    try:
        c.disable_user(username="alice", home_dir="/private/users/alice")
        assert False, "expected SFTPGoError"
    except SFTPGoError as e:
        assert "connection error" in str(e)
