"""Additional APIFunctions tests covering FOS CRUD, upload, dual-env and iperf
success paths (collaborators mocked)."""

from unittest.mock import MagicMock

import pytest

import functions.APIFunctions as apimod
from functions.APIFunctions import APIFunctions
from tests.conftest import FakeRequest

pytestmark = pytest.mark.unit


class FakeParser:
    def __init__(self, values):
        self._v = values

    def add_argument(self, *a, **k):
        pass

    def parse_args(self):
        return self._v


class FakeReqparse:
    def __init__(self, values):
        self._v = values

    def RequestParser(self):
        return FakeParser(self._v)


@pytest.fixture
def api(mocker):
    mocker.patch.object(apimod, "APILogger", return_value=MagicMock())
    instance = APIFunctions(env="prod")
    instance.set_env("prod")
    return instance


def _body(data=b"x"):
    b = MagicMock()
    b.read.return_value = data
    return b


def _ok_s3(data=b"x"):
    """A MagicMock S3 client whose operations all succeed."""
    s3 = MagicMock()
    s3.download_object.return_value = (True, None)
    s3.upload_object.return_value = (True, MagicMock(status_code=200))
    s3.set_object_acl.return_value = (True, "acl set")
    s3.get_object.return_value = (
        True,
        {"ResponseMetadata": {"HTTPStatusCode": 200}, "Body": _body(data)},
    )
    s3.delete_object.return_value = (True, "deleted")
    s3.get_list_object.return_value = (True, [{"Key": "a"}, {"Key": "b"}])
    return s3


@pytest.fixture(autouse=True)
def in_tmp(monkeypatch, tmp_path):
    monkeypatch.chdir(tmp_path)


# ── put_test_fos ─────────────────────────────────────────────────────────────
def test_put_test_fos_success(api, mocker):
    mocker.patch.object(apimod, "S3Client", return_value=_ok_s3(b"hello"))
    req = FakeRequest(json_body={"data": "hello"})
    rp = FakeReqparse({"data": "hello"})
    body, code = api.put_test_fos(rp, req, "bucket", "obj")
    assert code == 200


def test_put_test_fos_download_fail(api, mocker):
    s3 = _ok_s3()
    s3.download_object.return_value = (
        False,
        {"ResponseMetadata": {"HTTPStatusCode": 404}, "Error": {"Message": "missing"}},
    )
    mocker.patch.object(apimod, "S3Client", return_value=s3)
    req = FakeRequest(json_body={"data": "hi"})
    rp = FakeReqparse({"data": "hi"})
    _, code = api.put_test_fos(rp, req, "bucket", "obj")
    assert code == 404


# ── test_fos_crud ────────────────────────────────────────────────────────────
def test_fos_crud_full_pass(api, mocker):
    mocker.patch.object(apimod, "S3Client", return_value=_ok_s3(b"data"))
    mocker.patch.object(apimod.time, "sleep")  # skip the real sleeps
    # CREATE writes a fixed init string then UPDATE writes the input; object
    # read-back validation is exercised separately, so stub it here to focus on
    # the CRUD orchestration path.
    mocker.patch.object(api, "_validate_get_fos_object", return_value=(True, None))
    req = FakeRequest(json_body={"env": "prod", "data": "data"})
    rp = FakeReqparse({"data": "data"})
    out = api.test_fos_crud(rp, req, "bucket", "obj")
    assert out["code"] == 200 and "CRUD" in out["msg"]


# ── create_fos_object ────────────────────────────────────────────────────────
def test_create_fos_object_success(api, mocker):
    mocker.patch.object(apimod, "S3Client", return_value=_ok_s3(b"payload"))
    req = FakeRequest(json_body={"env": "prod", "data": "payload"})
    rp = FakeReqparse({"data": "payload"})
    out = api.create_fos_object(rp, req, "bucket", "obj")
    assert out["code"] == 200 and "Upload" in out["msg"]


# ── delete_fos_object ────────────────────────────────────────────────────────
def test_delete_fos_object_success(api, mocker):
    mocker.patch.object(apimod, "S3Client", return_value=_ok_s3())
    req = FakeRequest(args={"env": "prod"})
    out = api.delete_fos_object(req, "bucket", "obj")
    assert out["code"] == 200 and "DELETE" in out["msg"]


def test_delete_fos_object_not_found(api, mocker):
    s3 = _ok_s3()
    s3.get_object.return_value = (
        False,
        {"ResponseMetadata": {"HTTPStatusCode": 404}, "Error": {"Message": "nope"}},
    )
    mocker.patch.object(apimod, "S3Client", return_value=s3)
    req = FakeRequest(args={"env": "prod"})
    out = api.delete_fos_object(req, "bucket", "obj")
    assert out["code"] == 404


# ── download_object ──────────────────────────────────────────────────────────
def test_download_object_success(api, mocker):
    mocker.patch.object(apimod, "S3Client", return_value=_ok_s3())
    req = FakeRequest(args={"env": "prod"})
    out = api.download_object(req, "bucket", "obj")
    assert out["code"] == 200 and "Download" in out["msg"]


# ── _file_write_and_upload_fos failure branches ──────────────────────────────
def test_file_upload_fails(api, mocker):
    s3 = _ok_s3()
    s3.upload_object.return_value = (False, MagicMock(status_code=500, text="err"))
    ok, resp = api._file_write_and_upload_fos("f.txt", "data", "bucket", "obj", s3, FakeRequest())
    assert ok is False and resp["code"] == 500


def test_validate_get_fos_object_mismatch(api):
    s3 = _ok_s3(b"actual")
    ok, resp = api._validate_get_fos_object(s3, "bucket", "obj", "expected")
    assert ok is False and "validate" in resp["msg"]


# ── dual-env write / list success ────────────────────────────────────────────
def test_write_to_dual_env_success(api, mocker):
    mocker.patch.object(apimod, "S3Client", return_value=_ok_s3(b"payload"))
    req = FakeRequest(json_body={"data": "payload"})
    rp = FakeReqparse({"data": "payload"})
    out = api.write_to_dual_env(rp, req, "bucket", "obj")
    assert out["overall_success"] is True and out["code"] == 200


def test_list_from_dual_env_success(api, mocker):
    mocker.patch.object(apimod, "S3Client", return_value=_ok_s3())
    out = api.list_from_dual_env(FakeRequest(), "bucket")
    assert out["overall_success"] is True
    assert out["environments"]["prod"]["count"] == 2


# ── dual mysql query success ─────────────────────────────────────────────────
def test_execute_dual_mysql_query_success(api, mocker):
    temp = MagicMock()
    temp.connect.return_value = {"code": 200}
    temp.execute_query.return_value = {
        "code": 200,
        "msg": "ok",
        "columns_names": ["c"],
        "results": [[1]],
    }
    mocker.patch.object(apimod, "MySQLHandler", return_value=temp)
    body = {
        "query": "SELECT 1",
        "ssk": {"service": "s", "user": "u", "password": "p"},
        "kks": {"service": "s2", "user": "u2", "password": "p2"},
    }
    out = api.execute_dual_mysql_query(FakeRequest(json_body=body))
    assert out["overall_success"] is True and out["code"] == 200


# ── iperf success paths ──────────────────────────────────────────────────────
def _iperf_result(**kw):
    res = MagicMock()
    res.error = None
    for k, v in kw.items():
        setattr(res, k, v)
    return res


def test_run_iperf_tcp_success(api, mocker):
    mocker.patch.object(api, "_is_iperf_installed", return_value=True)
    client = MagicMock()
    client.run.return_value = _iperf_result(
        sent_bytes=2_000_000,
        received_bytes=2_000_000,
        sent_bps=1_000_000,
        received_bps=1_000_000,
        duration=10,
    )
    client.protocol = "tcp"
    mocker.patch.object(apimod.iperf3, "Client", return_value=client)
    params = {"host_ip": "1.1.1.1", "host_port": "5201", "duration": 10, "protocol": "tcp"}
    body, code = api.run_iperf(FakeReqparse(params), FakeRequest())
    assert code == 200 and "TCP Result" in body


def test_run_iperf_udp_success(api, mocker):
    mocker.patch.object(api, "_is_iperf_installed", return_value=True)
    client = MagicMock()
    client.run.return_value = _iperf_result(
        bytes=2_000_000, bps=1_000_000, duration=10, lost_percent=0.5
    )
    client.protocol = "udp"
    mocker.patch.object(apimod.iperf3, "Client", return_value=client)
    params = {"host_ip": "1.1.1.1", "host_port": "5201", "duration": 10, "protocol": "udp"}
    body, code = api.run_iperf(FakeReqparse(params), FakeRequest())
    assert code == 200 and "UDP Result" in body


def test_run_iperf_error_in_result(api, mocker):
    mocker.patch.object(api, "_is_iperf_installed", return_value=True)
    client = MagicMock()
    res = MagicMock()
    res.error = "connection refused"
    client.run.return_value = res
    mocker.patch.object(apimod.iperf3, "Client", return_value=client)
    params = {"host_ip": "1.1.1.1", "host_port": "5201", "duration": 10, "protocol": "tcp"}
    _, code = api.run_iperf(FakeReqparse(params), FakeRequest())
    assert code == 500
