#
#   Copyright 2022 Logical Clocks AB
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
from __future__ import annotations

import json
from typing import TYPE_CHECKING, Literal

import humps
from hopsworks_apigen import public
from hopsworks_common import alert, client, util
from hopsworks_common.core import (
    alerts_api,
    dataset_api,
    environment_api,
    flink_cluster_api,
    git_api,
    job_api,
    kafka_api,
    opensearch_api,
    search_api,
    trino_api,
)


if TYPE_CHECKING:
    from hsfs.feature_store import FeatureStore
    from hsml.model_registry import ModelRegistry
    from hsml.model_serving import ModelServing


@public("hopsworks.project.Project")
class Project:
    """Class representing a Hopsworks project.

    Use [`hopsworks.login`][hopsworks.login] to get the current project after logging in.

    Use [`hopsworks.create_project`][hopsworks.create_project] to create a new project and get the project object.
    """

    def __init__(
        self,
        archived=None,
        created=None,
        description=None,
        docker_image=None,
        hops_examples=None,
        inodeid=None,
        is_old_docker_image=None,
        is_preinstalled_docker_image=None,
        owner=None,
        project_id=None,
        project_name=None,
        project_team=None,
        quotas=None,
        retention_period=None,
        services=None,
        datasets=None,
        creation_status=None,
        project_namespace=None,
        **kwargs,
    ):
        self._id = project_id
        self._name = project_name
        self._owner = owner
        self._description = description
        self._created = created

        self._opensearch_api = opensearch_api.OpenSearchApi()
        self._kafka_api = kafka_api.KafkaApi()
        self._job_api = job_api.JobApi()
        self._jobs_api = self._job_api  # deprecated
        self._flink_cluster_api = flink_cluster_api.FlinkClusterApi()
        self._git_api = git_api.GitApi()
        self._dataset_api = dataset_api.DatasetApi()
        self._environment_api = environment_api.EnvironmentApi()
        self._alerts_api = alerts_api.AlertsApi()
        self._search_api = search_api.SearchApi()
        self._project_namespace = project_namespace
        self._trino_api = None

    @classmethod
    def from_response_json(cls, json_dict):
        if json_dict:
            json_decamelized = humps.decamelize(json_dict)
            return cls(**json_decamelized)
        return None

    @public
    @property
    def id(self):
        """Id of the project."""
        return self._id

    @public
    @property
    def name(self):
        """Name of the project."""
        return self._name

    @public
    @property
    def owner(self):
        """Owner of the project."""
        return self._owner

    @public
    @property
    def description(self):
        """Description of the project."""
        return self._description

    @public
    @property
    def created(self):
        """Timestamp when the project was created."""
        return self._created

    @public
    @property
    def project_namespace(self):
        """Kubernetes namespace used by project."""
        return self._project_namespace

    @public
    def get_feature_store(self, name: str | None = None) -> FeatureStore:
        """Connect to Project's Feature Store.

        Defaulting to the project name of default feature store. To get a
        shared feature store, the project name of the feature store is required.

        Example: Example for getting the Feature Store API of a project
            ```python
            import hopsworks

            project = hopsworks.login()

            fs = project.get_feature_store()
            ```

        Parameters:
            name: Project name of the feature store.

        Returns:
            The Feature Store API.

        Raises:
            hopsworks.client.exceptions.RestAPIError: If the backend encounters an error when handling the request.
        """
        return client.get_connection().get_feature_store(name)

    @public
    def get_model_registry(self) -> ModelRegistry:
        """Connect to Project's Model Registry API.

        Example: Example for getting the Model Registry API of a project
            ```python
            import hopsworks

            project = hopsworks.login()

            mr = project.get_model_registry()
            ```

        Returns:
            The Model Registry API.

        Raises:
            hopsworks.client.exceptions.RestAPIError: If the backend encounters an error when handling the request.
        """
        return client.get_connection().get_model_registry()

    @public
    def get_model_serving(self) -> ModelServing:
        """Connect to Project's Model Serving API.

        Example: Example for getting the Model Serving API of a project
            ```python
            import hopsworks

            project = hopsworks.login()

            ms = project.get_model_serving()
            ```

        Returns:
            The Model Serving API.

        Raises:
            hopsworks.client.exceptions.RestAPIError: If the backend encounters an error when handling the request.
        """
        return client.get_connection().get_model_serving()

    @public
    def get_kafka_api(self) -> kafka_api.KafkaApi:
        """Get the kafka api for the project.

        Returns:
            The Kafka Api handle.
        """
        _client = client.get_instance()
        if _client._is_external():
            _client.download_certs()
        return self._kafka_api

    @public
    def get_opensearch_api(self) -> opensearch_api.OpenSearchApi:
        """Get the opensearch api for the project.

        Returns:
            The OpenSearch Api handle.
        """
        _client = client.get_instance()
        if _client._is_external():
            _client.download_certs()
        return self._opensearch_api

    @public
    def get_job_api(self) -> job_api.JobApi:
        """Get the job API for the project.

        Returns:
            The Job Api handle.
        """
        return self._job_api

    def get_jobs_api(self):
        """**Deprecated**, use get_job_api instead. Excluded from docs to prevent API breakage."""
        return self.get_job_api()

    @public
    def get_flink_cluster_api(self) -> flink_cluster_api.FlinkClusterApi:
        """Get the flink cluster API for the project.

        Returns:
            The Flink Cluster Api handle.
        """
        return self._flink_cluster_api

    @public
    def get_git_api(self) -> git_api.GitApi:
        """Get the git repository api for the project.

        Returns:
            The Git Api handle.
        """
        return self._git_api

    @public
    def get_dataset_api(self) -> dataset_api.DatasetApi:
        """Get the dataset api for the project.

        Returns:
            The Datasets Api handle.
        """
        return self._dataset_api

    @public
    def get_environment_api(self) -> environment_api.EnvironmentApi:
        """Get the Python environment API for the project.

        Returns:
            The Python Environment Api handle.
        """
        return self._environment_api

    @public
    def get_alerts_api(self) -> alerts_api.AlertsApi:
        """Get the alerts api for the project.

        Returns:
            The Alerts Api handle.
        """
        return self._alerts_api

    @public
    def get_search_api(self) -> search_api.SearchApi:
        """Get the search api for the project.

        Returns:
            The Search Api handle.
        """
        return self._search_api

    @public
    def get_trino_api(self) -> trino_api.TrinoApi:
        """Get the Trino API for the project.

        Returns:
            The Trino API handle.
        """
        if self._trino_api is None:
            self._trino_api = trino_api.TrinoApi(project=self)
        return self._trino_api

    @public
    def get_alerts(self) -> list[alert.ProjectAlert]:
        """Get all alerts for the project.

        Returns:
            List of `ProjectAlert` objects.

        Raises:
            hopsworks.client.exceptions.RestAPIError: If the backend encounters an error when handling the request.
        """
        return self._alerts_api.get_alerts()

    @public
    def get_alert(self, alert_id: int) -> alert.ProjectAlert | None:
        """Get an alert for the project by ID.

        Parameters:
            alert_id: The ID of the alert.

        Returns:
            The ProjectAlert object.
        """
        return self._alerts_api.get_alert(alert_id)

    @public
    def create_job_alert(
        self,
        receiver: str,
        status: Literal["job_finished", "job_failed", "job_killed", "job_long_running"],
        severity: Literal["critical", "warning", "info"],
    ) -> alert.ProjectAlert:
        """Create an alert for jobs in this project.

        Example: Example for creating a job alert
            ```python
            import hopsworks
            project = hopsworks.login()
            project.create_job_alert("my_receiver", "long_running", "info")
            ```

        Parameters:
            receiver: The receiver of the alert.
            status: The status of the alert.
            severity: The severity of the alert.

        Returns:
            The created `ProjectAlert` object.

        Raises:
            ValueError: If `status` or `severity` is invalid, also if `receiver` is `None`.
            hopsworks.client.exceptions.RestAPIError: If the backend encounters an error when handling the request.
        """
        return self._alerts_api.create_project_alert(receiver, status, severity, "Jobs")

    @public
    def create_featurestore_alert(
        self,
        receiver: str,
        status: Literal[
            "feature_validation_success",
            "feature_validation_warning",
            "feature_validation_failure",
            "feature_monitor_shift_undetected",
            "feature_monitor_shift_detected",
        ],
        severity: Literal["critical", "warning", "info"],
    ) -> alert.ProjectAlert:
        """Create an alert for feature validation and monitoring in this project.

        Example: Example for creating a featurestore alert
            ```python
            import hopsworks
            project = hopsworks.login()
            project.create_featurestore_alert("my_receiver", "feature_validation_success", "info")
            ```

        Parameters:
            receiver: The receiver of the alert.
            status: The status of the alert.
            severity: The severity of the alert.

        Returns:
            The created `ProjectAlert` object.

        Raises:
            ValueError: If `status` or `severity` is invalid, also if `receiver` is `None`.
            hopsworks.client.exceptions.RestAPIError: If the backend encounters an error when handling the request.
        """
        return self._alerts_api.create_project_alert(
            receiver, status, severity, "Featurestore"
        )

    def json(self):
        return json.dumps(self, cls=util.Encoder)

    def __str__(self):
        return self.json()

    def __repr__(self):
        if self._description is not None:
            return f"Project({self._name!r}, {self._owner!r}, {self._description!r})"
        return f"Project({self._name!r}, {self._owner!r})"

    @public
    def get_url(self):
        """Get url to the project in Hopsworks."""
        path = "/p/" + str(self.id)
        return util.get_hostname_replaced_url(path)
