"""Unit tests for lib/s3_client.py with boto3 / requests mocked."""

from unittest.mock import MagicMock, mock_open

import pytest
from botocore.exceptions import ClientError

from lib.s3_client import S3Client

pytestmark = pytest.mark.unit


@pytest.fixture
def client(mocker):
    mocker.patch("lib.s3_client.boto3.client", return_value=MagicMock())
    return S3Client("ak", "sk", env="prod")


def _client_error():
    return ClientError(
        {"Error": {"Message": "boom"}, "ResponseMetadata": {"HTTPStatusCode": 404}},
        "GetObject",
    )


def test_invalid_env_raises(mocker):
    mocker.patch("lib.s3_client.boto3.client", return_value=MagicMock())
    with pytest.raises(ValueError):
        S3Client("ak", "sk", env="bogus")


def test_endpoint_selected(client):
    assert "prod" in client.fos_endpoint


def test_download_object_ok(client):
    client.s3_client.download_file.return_value = None
    ok, _ = client.download_object("b", "o", "dst")
    assert ok is True


def test_download_object_client_error(client):
    client.s3_client.download_file.side_effect = _client_error()
    ok, resp = client.download_object("b", "o", "dst")
    assert ok is False and resp["ResponseMetadata"]["HTTPStatusCode"] == 404


def test_get_object_ok(client):
    client.s3_client.get_object.return_value = {"Body": "x"}
    ok, resp = client.get_object("b", "o")
    assert ok is True and resp == {"Body": "x"}


def test_get_object_error(client):
    client.s3_client.get_object.side_effect = _client_error()
    ok, _ = client.get_object("b", "o")
    assert ok is False


def test_upload_object_success(client, mocker):
    client.s3_client.generate_presigned_url.return_value = "http://url"
    mocker.patch("builtins.open", mock_open(read_data=b"data"))
    mocker.patch("lib.s3_client.requests.put", return_value=MagicMock(status_code=200))
    ok, _ = client.upload_object("b", "o", "src")
    assert ok is True


def test_upload_object_failure(client, mocker):
    client.s3_client.generate_presigned_url.return_value = "http://url"
    mocker.patch("builtins.open", mock_open(read_data=b"data"))
    mocker.patch("lib.s3_client.requests.put", return_value=MagicMock(status_code=500))
    ok, _ = client.upload_object("b", "o", "src")
    assert ok is False


def test_set_object_acl_ok(client):
    ok, msg = client.set_object_acl("b", "o")
    assert ok is True and "ACL" in msg


def test_set_object_acl_failure(client):
    client.s3_client.put_object_acl.side_effect = RuntimeError("nope")
    ok, msg = client.set_object_acl("b", "o")
    assert ok is False and "nope" in msg


def test_delete_object_success(client, mocker):
    client.s3_client.generate_presigned_url.return_value = "http://url"
    mocker.patch("lib.s3_client.requests.delete", return_value=MagicMock(status_code=204))
    ok, _ = client.delete_object("b", "o")
    assert ok is True


def test_delete_object_failure(client, mocker):
    client.s3_client.generate_presigned_url.return_value = "http://url"
    mocker.patch(
        "lib.s3_client.requests.delete", return_value=MagicMock(status_code=500, text="err")
    )
    ok, _ = client.delete_object("b", "o")
    assert ok is False


def test_get_list_object_ok(client):
    client.s3_client.list_objects_v2.return_value = {"Contents": [{"Key": "a"}]}
    ok, objs = client.get_list_object("b")
    assert ok is True and objs == [{"Key": "a"}]


def test_get_list_object_client_error(client):
    client.s3_client.list_objects_v2.side_effect = _client_error()
    ok, _ = client.get_list_object("b")
    assert ok is False


def test_update_bucket_enable(client):
    assert client.update_bucket("b", "enable")["code"] == 200


def test_update_bucket_disable(client):
    assert client.update_bucket("b", "disable")["code"] == 200


def test_update_bucket_error(client):
    client.s3_client.put_bucket_website.side_effect = RuntimeError("x")
    assert client.update_bucket("b", "enable")["code"] == 500
