import json
import os
from pathlib import Path
from typing import Any, Union

import requests
from jsonschema import validate
from jsonschema.exceptions import ValidationError
from referencing import Registry, Resource


def fetch_remote(uri):
    response = requests.get(uri)
    response.raise_for_status()
    return Resource.from_contents(response.json())


class RELAIData:
    """Base class for RELAI data models. Validations that are performed directly on data passed to a child class
    should be done before calling `__init__` of this base class.

    To enable strict schema validation, set the environment variable `RELAI_DEVELOPMENT_MODE` to `DEBUG`.

    Args:
        **kwargs: Data to be validated against the schema.
    """

    schema_file: Union[str, Path] = None  # Path to the schema file for this component

    def __init__(self, **kwargs) -> None:
        with open(self.schema_file, "r") as f:
            self._schema = json.load(f)

        self._data = kwargs
        # Remove keys with None values
        self._data = {k: v for k, v in self._data.items() if v is not None}

        # Remote schema registry
        self._remote_schema_registry = Registry(
            resources={
                "vega": "https://vega.github.io/schema/vega/v6.json",
                "vega-lite": "https://vega.github.io/schema/vega-lite/v6.json",
            },
            retrieve=fetch_remote,
        )

        relai_development_mode = os.getenv("RELAI_DEVELOPMENT_MODE", "DEBUG")
        if relai_development_mode == "DEBUG":
            self.schema_validation()
            self.custom_validation()
        elif relai_development_mode != "PRODUCTION":
            raise ValueError("Invalid value for RELAI_DEVELOPMENT_MODE. Must be 'DEBUG' or 'PRODUCTION'.")

    @property
    def data(self) -> dict:
        """Get the data. This property is read-only."""
        return self._data

    def schema_validation(self) -> None:
        """Validate the data against the schema."""
        try:
            validate(
                instance=self._data,
                schema=self._schema,
                registry=self._remote_schema_registry,
            )
        except ValidationError as e:
            raise ValueError(f"Data validation failed: {e.message}") from e

    def custom_validation(self) -> None:
        """Custom validation logic. Use this to validate the JSON data dict against custom rules,
        after schema validation."""
        pass

    def json(self) -> str:
        """Serialize the data to a JSON formatted `str`."""
        return json.dumps(self._data)

    @classmethod
    def user_feedback(cls, component: dict) -> dict:
        """Get the feedback provided by the user for the component.

        Args:
            component (dict): Component data.
        """
        raise NotImplementedError

    def __str__(self) -> str:
        return self.json()

    @classmethod
    def update_with_feedback(cls, component: dict, modifies: list[str], new_value: Any) -> dict:
        """Update a `RELAIData` component with user feedback.

        Args:
            component (dict): Component to update as a Python dictionary.
            modifies (list[str]): List of strings that uniquely identify the data to be updated.
            new_value (Any): New value to set.

        Returns:
            dict: Updated component.
        """
        raise NotImplementedError

    @classmethod
    def from_data(cls, data: dict) -> "RELAIData":
        """Create a new instance of the class from the data dictionary.

        Args:
            data (dict): Data dictionary.

        Returns:
            RELAIData: Instance of the class.
        """
        component = cls.__new__(cls)
        super(cls, component).__init__(**data)
        return component
