"""Unit tests for the DB base handler, Redis handler, and Mongo mapping data."""

from unittest.mock import MagicMock

import pytest

from products.dbs import mongo_constants, mongo_mappings
from products.dbs.base_db import BaseDBHandler
from products.dbs.redis import RedisHandler

pytestmark = pytest.mark.unit


# ── base_db response helpers ─────────────────────────────────────────────────
class _Concrete(BaseDBHandler):
    def connect(self, service, user, password, database=None):
        return self.make_connection_response("ok")

    def execute_query(self, query, database=None):
        return self.make_success_response()


@pytest.fixture
def handler():
    return _Concrete(api_logger=MagicMock())


def test_success_response_defaults(handler):
    r = handler.make_success_response()
    assert r == {
        "code": 200,
        "msg": "Query executed successfully",
        "columns_names": [],
        "results": [],
    }


def test_success_response_custom(handler):
    r = handler.make_success_response("done", ["a"], [[1]])
    assert r["msg"] == "done" and r["columns_names"] == ["a"] and r["results"] == [[1]]


def test_connection_and_error_responses(handler):
    assert handler.make_connection_response("up") == {"code": 200, "msg": "up"}
    assert handler.make_error_response(500, "boom") == {"code": 500, "msg": "boom"}


def test_concrete_uses_helpers(handler):
    assert handler.connect("s", "u", "p")["msg"] == "ok"
    assert handler.execute_query("q")["code"] == 200


# ── mongo_constants / mongo_mappings ─────────────────────────────────────────
def test_connection_templates_format():
    tmpl = mongo_constants.CONNECTION_TEMPLATES["with_auth"].format(
        user="u", password="p", service="svc", database="db"
    )
    assert tmpl.startswith("mongodb+srv://u:p@svc/db")


def test_column_names_cover_known_ops():
    assert mongo_constants.COLUMN_NAMES["count"] == ["count"]
    assert "version" in mongo_constants.COLUMN_NAMES["build_info"]


def test_collection_method_mapping_lambdas():
    m = mongo_mappings.COLLECTION_METHOD_MAPPING
    assert m["find"]("c", [{"x": 1}]) == {"find": "c", "filter": {"x": 1}}
    assert m["findOne"]("c", [])["limit"] == 1
    assert m["insertOne"]("c", [{"a": 1}]) == {"insert": "c", "documents": [{"a": 1}]}
    assert m["updateOne"]("c", [{"q": 1}, {"u": 2}])["updates"][0]["multi"] is False
    assert m["deleteMany"]("c", [{"q": 1}])["deletes"][0]["limit"] == 0
    assert m["count"]("c", [{"q": 1}]) == {"count": "c", "query": {"q": 1}}
    assert m["aggregate"]("c", [[{"$match": {}}]])["pipeline"] == [{"$match": {}}]


def test_command_processors_serverstatus():
    proc = mongo_mappings.COMMAND_PROCESSORS["serverStatus"]["processor"]
    out = proc({"uptime": 5, "connections": {"current": 2, "available": 8}})
    assert out == [[5, 2, 8]]


def test_show_and_method_mappings():
    assert mongo_mappings.SHOW_MAPPING["dbs"] == {"listDatabases": 1}
    assert mongo_mappings.DB_METHOD_MAPPING["stats"] == {"dbStats": 1}
    assert mongo_mappings.RS_METHOD_MAPPING["status"] == {"replSetGetStatus": 1}
    assert mongo_mappings.SH_METHOD_MAPPING["status"] == {"shardingState": 1}


# ── Redis handler ────────────────────────────────────────────────────────────
@pytest.fixture
def redis_handler():
    return RedisHandler(MagicMock())


def test_redis_connect_success(redis_handler, mocker):
    client = MagicMock()
    mocker.patch("products.dbs.redis.redis.Redis", return_value=client)
    res = redis_handler.connect("svc", "user", "pw")
    assert res["code"] == 200
    client.ping.assert_called_once()


def test_redis_connect_failure(redis_handler, mocker):
    mocker.patch("products.dbs.redis.redis.Redis", side_effect=RuntimeError("nope"))
    res = redis_handler.connect("svc", "user", "pw")
    assert res["code"] == 500 and "nope" in res["msg"]


def test_redis_execute_empty_query(redis_handler):
    assert redis_handler.execute_query("   ")["code"] == 400


def test_redis_execute_list_result(redis_handler):
    redis_handler.redis_client = MagicMock()
    redis_handler.redis_client.execute_command.return_value = ["a", "b"]
    res = redis_handler.execute_query("KEYS *")
    assert res["code"] == 200 and res["results"] == [["a"], ["b"]]


def test_redis_execute_dict_result(redis_handler):
    redis_handler.redis_client = MagicMock()
    redis_handler.redis_client.execute_command.return_value = {"k": "v"}
    res = redis_handler.execute_query("HGETALL h")
    assert res["columns_names"] == ["field", "value"]
    assert res["results"] == [["k", "v"]]


def test_redis_execute_none_result(redis_handler):
    redis_handler.redis_client = MagicMock()
    redis_handler.redis_client.execute_command.return_value = None
    res = redis_handler.execute_query("GET missing")
    assert res["results"] == [["(nil)"]]


def test_redis_execute_scalar_result(redis_handler):
    redis_handler.redis_client = MagicMock()
    redis_handler.redis_client.execute_command.return_value = "PONG"
    res = redis_handler.execute_query("PING")
    assert res["results"] == [["PONG"]]


def test_redis_execute_command_error(redis_handler):
    redis_handler.redis_client = MagicMock()
    redis_handler.redis_client.execute_command.side_effect = RuntimeError("bad cmd")
    res = redis_handler.execute_query("GET x")
    assert res["code"] == 400 and "bad cmd" in res["msg"]
