import json
import logging
from functools import lru_cache

from cassandra import __version__ as PYTHON_DRIVER_VERSION
from argus.client.sct.client import ArgusSCTClient
from argus.client.sct.types import Package

from sdcm.remote.base import CommandRunner
from sdcm.utils.vector_store_client import VectorStoreClient

LOGGER = logging.getLogger(__name__)


@lru_cache
def report_package_to_argus(
    client: ArgusSCTClient,
    tool_name: str,
    package_version: str,
    additional_data: str = None,
    date: str = None,
    revision_id: str = None,
) -> None:
    package = Package(
        name=f"{tool_name}", version=package_version, date=date, revision_id=revision_id, build_id=additional_data
    )
    client.submit_packages([package])


class ToolReporterBase:
    TOOL_NAME = None

    def __init__(self, runner: CommandRunner, command_prefix: str = None, argus_client: ArgusSCTClient = None) -> None:
        self.runner = runner
        self.command_prefix = command_prefix
        self.argus_client = argus_client
        self.additional_data = None
        self.version: str | None = None
        self.date: str | None = None
        self.revision_id: str | None = None

    def __str__(self) -> str:
        return f"{self.__class__.__name__}()"

    def _report_to_log(self) -> None:
        LOGGER.info("%s: %s version is %s", self, self.TOOL_NAME, self.version)

    def _report_to_argus(self) -> None:
        if not self.argus_client:
            LOGGER.warning("%s: Skipping reporting to argus, client not initialized.", self)
            return
        try:
            report_package_to_argus(
                client=self.argus_client,
                tool_name=self.TOOL_NAME,
                package_version=self.version,
                additional_data=self.additional_data,
                date=self.date,
                revision_id=self.revision_id,
            )
        except Exception:  # noqa: BLE001
            LOGGER.warning("Failed reporting tool version to Argus", exc_info=True)

    def _collect_version_info(self) -> None:
        raise NotImplementedError()

    def report(self) -> None:
        self._collect_version_info()
        if not self.version:
            LOGGER.warning("%s: Version not collected, skipping report...", self)
            return
        self._report_to_log()
        self._report_to_argus()


class PythonDriverReporter(ToolReporterBase):
    """
    Reports python-driver version used for SCT operations.
    """

    TOOL_NAME = "scylla-cluster-tests/python-driver"

    def __init__(self, argus_client: ArgusSCTClient = None) -> None:
        super().__init__(None, "", argus_client)

    def _collect_version_info(self):
        self.version = PYTHON_DRIVER_VERSION


class CassandraStressVersionReporter(ToolReporterBase):
    TOOL_NAME = "cassandra-stress"

    def __init__(
        self,
        runner: CommandRunner,
        command_prefix: str = None,
        argus_client: ArgusSCTClient = None,
        is_driver_4x: bool = False,
    ) -> None:
        super().__init__(runner, command_prefix, argus_client)
        self.is_driver_4x = is_driver_4x

    def _collect_version_info(self) -> None:
        output = self.runner.run(f"{self.command_prefix} {self.TOOL_NAME} version")
        LOGGER.info("%s: Collected cassandra-stress output:\n%s", self, output.stdout)
        field_map = {
            "Version": "cassandra-stress",
            "scylla-java-driver": "scylla-java-driver",
            "scylla-java-driver-4x": "scylla-java-driver-4x",
        }
        result = {}
        for line in output.stdout.splitlines():
            try:
                key, value = line.split(":", 2)
                if not (field_name := field_map.get(key)):
                    continue
                result[field_name] = value.strip()
            except ValueError:
                continue
        LOGGER.info("Result:\n%s", result)
        self.version = f"{result.get('cassandra-stress', '#FAILED_CHECK_LOGS')}"
        # Report the driver version that is actually being used based on is_driver_4x flag
        if self.is_driver_4x and (driver_version := result.get("scylla-java-driver-4x")):
            self.additional_data = f"java-driver-4x: {driver_version}"
            CassandraStressJavaDriverVersionReporter(
                driver_version=driver_version, command_prefix=None, runner=None, argus_client=self.argus_client
            ).report()
        elif not self.is_driver_4x and (driver_version := result.get("scylla-java-driver")):
            self.additional_data = f"java-driver: {driver_version}"
            CassandraStressJavaDriverVersionReporter(
                driver_version=driver_version, command_prefix=None, runner=None, argus_client=self.argus_client
            ).report()


class CqlStressCassandraStressVersionReporter(ToolReporterBase):
    TOOL_NAME = "cql-stress-cassandra-stress"

    def _collect_version_info(self) -> None:
        output = self.runner.run(f"{self.command_prefix} {self.TOOL_NAME} version_json")
        LOGGER.debug("%s: Collected cql-stress-cassandra-stress output:\n%s", self, output.stdout)
        result = json.loads(output.stdout)

        LOGGER.debug("Result:\n%s", result)
        cql_stress_details = result.get("cql-stress", {})
        self.version = f"{cql_stress_details.get('version', '#FAILED_CHECK_LOGS')}"
        self.date = cql_stress_details.get("commit_date")
        self.revision_id = cql_stress_details.get("commit_sha")

        if driver_details := result.get("scylla-driver", {}):
            driver_version = driver_details.get("version")
            driver_date = driver_details.get("commit_date")
            driver_revision_id = driver_details.get("commit_sha")
            CqlStressRustDriverVersionReporter(
                driver_version=driver_version,
                date=driver_date,
                revision_id=driver_revision_id,
                command_prefix=None,
                runner=None,
                argus_client=self.argus_client,
            ).report()


class LatteVersionReporter(ToolReporterBase):
    TOOL_NAME = "latte"

    def _collect_version_info(self) -> None:
        output = self.runner.run(f"{self.command_prefix} {self.TOOL_NAME} version -j")
        LOGGER.debug("%s: Collected latte version output:\n%s", self, output.stdout)
        result = json.loads(output.stdout)
        LOGGER.debug("Result:\n%s", result)
        latte_details = result.get("latte", {})
        self.version = latte_details.get("version", "#FAILED_CHECK_LOGS")
        self.date = latte_details.get("commit_date")
        self.revision_id = latte_details.get("commit_sha")
        if driver_details := result.get("scylla-driver", {}):
            LatteRustDriverVersionReporter(
                driver_version=driver_details.get("version"),
                date=driver_details.get("commit_date"),
                revision_id=driver_details.get("commit_sha"),
                argus_client=self.argus_client,
            ).report()


class CassandraStressJavaDriverVersionReporter(ToolReporterBase):
    TOOL_NAME = "java-driver"

    def __init__(
        self,
        driver_version: str,
        runner: CommandRunner,
        command_prefix: str = None,
        argus_client: ArgusSCTClient = None,
    ) -> None:
        super().__init__(runner, command_prefix, argus_client)
        self.version = driver_version

    def _collect_version_info(self) -> None:
        pass


class CqlStressRustDriverVersionReporter(ToolReporterBase):
    TOOL_NAME = "cql-stress-rust-driver"

    def __init__(
        self,
        driver_version: str,
        date: str,
        revision_id: str,
        runner: CommandRunner,
        command_prefix: str = None,
        argus_client: ArgusSCTClient = None,
    ) -> None:
        super().__init__(runner, command_prefix, argus_client)
        self.version = driver_version
        self.date = date
        self.revision_id = revision_id

    def _collect_version_info(self) -> None:
        pass


class LatteRustDriverVersionReporter(ToolReporterBase):
    TOOL_NAME = "latte-rust-driver"

    def __init__(self, driver_version: str, date: str, revision_id: str, argus_client: ArgusSCTClient = None) -> None:
        super().__init__(None, "", argus_client)
        self.version = driver_version
        self.date = date
        self.revision_id = revision_id

    def _collect_version_info(self) -> None:
        pass


class ScyllaBenchVersionReporter(ToolReporterBase):
    """
    Reports scylla-bench and scylla gocql driver versions used in SCT.
    """

    TOOL_NAME = "scylla-bench"

    def _collect_version_info(self) -> None:
        output = self.runner.run(f"{self.command_prefix} {self.TOOL_NAME} -version-json")
        LOGGER.debug("%s: Collected scylla-bench version output:\n%s", self, output.stdout)
        version_info = json.loads(output.stdout)
        LOGGER.debug("Result:\n%s", version_info)

        s_b_info = version_info.get("scylla-bench", {})
        self.version = f"{s_b_info.get('version', '#FAILED_CHECK_LOGS')}"
        self.date = s_b_info.get("commit_date")
        self.revision_id = s_b_info.get("commit_sha")

        if driver_details := version_info.get("scylla-driver", {}):
            ScyllaBenchGoCqlDriverVersionReporter(
                driver_version=driver_details.get("version"),
                date=driver_details.get("commit_date"),
                revision_id=driver_details.get("commit_sha"),
                argus_client=self.argus_client,
            ).report()


class ScyllaBenchGoCqlDriverVersionReporter(ToolReporterBase):
    TOOL_NAME = "scylla-bench-gocql-driver"

    def __init__(self, driver_version: str, date: str, revision_id: str, argus_client: ArgusSCTClient = None) -> None:
        super().__init__(None, "", argus_client)
        self.version = driver_version
        self.date = date
        self.revision_id = revision_id

    def _collect_version_info(self) -> None:
        pass


class GeminiVersionReporter(ToolReporterBase):
    """
    Reports Gemini and scylla gocql driver versions used in SCT.
    """

    TOOL_NAME = "gemini"

    def _collect_version_info(self) -> None:
        output = self.runner.run(f"{self.command_prefix} {self.TOOL_NAME} --version-json")
        LOGGER.debug("%s: Collected gemini version output:\n%s", self, output.stdout)
        version_info = json.loads(output.stdout)
        LOGGER.debug("Result:\n%s", version_info)

        s_b_info = version_info.get("gemini", {})
        self.version = f"{s_b_info.get('version', '#FAILED_CHECK_LOGS')}"
        self.date = s_b_info.get("commit_date")
        self.revision_id = s_b_info.get("commit_sha")

        if driver_details := version_info.get("scylla-driver", {}):
            GeminiGoCqlDriverVersionReporter(
                driver_version=driver_details.get("version"),
                date=driver_details.get("commit_date"),
                revision_id=driver_details.get("commit_sha"),
                argus_client=self.argus_client,
            ).report()


class GeminiGoCqlDriverVersionReporter(ToolReporterBase):
    TOOL_NAME = "gemini-gocql-driver"

    def __init__(self, driver_version: str, date: str, revision_id: str, argus_client: ArgusSCTClient = None) -> None:
        super().__init__(None, "", argus_client)
        self.version = driver_version
        self.date = date
        self.revision_id = revision_id

    def _collect_version_info(self) -> None:
        pass


class YcsbAwsSdkVersionReporter(ToolReporterBase):
    TOOL_NAME = "ycsb-aws-sdk"

    def __init__(self, version: str, argus_client: ArgusSCTClient = None) -> None:
        super().__init__(None, "", argus_client)
        self.version = version

    def _collect_version_info(self) -> None:
        pass


class YcsbScyllaDriverVersionReporter(ToolReporterBase):
    TOOL_NAME = "ycsb-scylla-driver"

    def __init__(self, version: str, argus_client: ArgusSCTClient = None) -> None:
        super().__init__(None, "", argus_client)
        self.version = version

    def _collect_version_info(self) -> None:
        pass


class YcsbVersionReporter(ToolReporterBase):
    """Reports YCSB version used in SCT."""

    TOOL_NAME = "ycsb"
    _YCSB_HOME = "/usr/local/share/scylla-ycsb"

    def __init__(
        self,
        runner: CommandRunner,
        command_prefix: str = None,
        argus_client: ArgusSCTClient = None,
        stress_cmd: str = "",
    ) -> None:
        super().__init__(runner, command_prefix, argus_client)
        self.stress_cmd = stress_cmd

    def _collect_version_info(self) -> None:
        output = self.runner.run(f"{self._YCSB_HOME}/bin/ycsb.sh --version")
        LOGGER.debug("%s: Collected ycsb version output:\n%s", self, output.stdout)
        version_info = json.loads(output.stdout.strip())

        self.version = version_info.get("version", "#FAILED_CHECK_LOGS")
        self.date = version_info.get("build_date")
        self.revision_id = version_info.get("git_hash")

        if "dynamodb" in self.stress_cmd and (aws_sdk_version := version_info.get("aws_sdk_version")):
            YcsbAwsSdkVersionReporter(version=aws_sdk_version, argus_client=self.argus_client).report()

        if "scylla" in self.stress_cmd and (scylla_driver_version := version_info.get("scylla_driver_version")):
            YcsbScyllaDriverVersionReporter(version=scylla_driver_version, argus_client=self.argus_client).report()


class VectorStoreVersionReporter(ToolReporterBase):
    TOOL_NAME = "vector-store"

    def __init__(self, vector_store_client: VectorStoreClient, argus_client: ArgusSCTClient = None) -> None:
        super().__init__(runner=None, command_prefix=None, argus_client=argus_client)
        self.vector_store_client = vector_store_client

    def _collect_version_info(self):
        info = self.vector_store_client.get_info()
        LOGGER.info("%s: Collected vector-store info: %s", self, info)
        self.version = info.get("version", "#FAILED_CHECK_LOGS")
