"""
    Copyright 2019 Inmanta

    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.

    Contact: code@inmanta.com
"""
import datetime
import uuid
from typing import Any, Dict, List, NewType, Optional, Union

import pydantic

from inmanta import const
from inmanta.const import Change, ResourceState
from inmanta.types import ArgumentTypes, JsonType, SimpleTypes, StrictNonIntBool


class BaseModel(pydantic.BaseModel):
    """
        Base class for all data objects in Inmanta
    """

    class Config:
        # Populate models with the value property of enums, rather than the raw enum.
        # This is useful to serialise model.dict() later
        use_enum_values = True


class ExtensionStatus(BaseModel):
    """
        Status response for extensions loaded in the server
    """

    name: str
    version: str
    package: str


class SliceStatus(BaseModel):
    """
        Status response for slices loaded in the the server
    """

    name: str
    status: Dict[str, ArgumentTypes]


class FeatureStatus(BaseModel):
    """
        Status of the feature
    """

    slice: str
    name: str
    value: Optional[Any]


class StatusResponse(BaseModel):
    """
        Response for the status method call
    """

    product: str
    edition: str
    version: str
    license: Union[str, Dict[str, SimpleTypes]]
    extensions: List[ExtensionStatus]
    slices: List[SliceStatus]
    features: List[FeatureStatus]


class CompileRun(BaseModel):
    id: uuid.UUID
    remote_id: Optional[uuid.UUID]
    environment: uuid.UUID
    requested: Optional[datetime.datetime]
    started: Optional[datetime.datetime]

    do_export: bool
    force_update: bool
    metadata: JsonType
    environment_variables: Dict[str, str]


ResourceVersionIdStr = NewType("ResourceVersionIdStr", str)
"""
    The resource id with the version included.
"""


ResourceIdStr = NewType("ResourceIdStr", str)
"""
    The resource id without the version
"""


ResourceType = NewType("ResourceType", str)
"""
    The type of the resource
"""


class AttributeStateChange(BaseModel):
    """
        Changes in the attribute
    """

    current: Optional[Union[SimpleTypes, List[SimpleTypes], Dict[str, SimpleTypes]]] = None
    desired: Optional[Union[SimpleTypes, List[SimpleTypes], Dict[str, SimpleTypes]]] = None


class Event(BaseModel):
    """
        An event in the list of events generated by dependencies during deploy
    """

    status: ResourceState
    change: Change
    changes: Dict[str, AttributeStateChange]


EnvSettingType = Union[StrictNonIntBool, int, str, Dict[str, Union[str, int, StrictNonIntBool]]]


class Environment(BaseModel):
    """
        An inmanta environment.

        :note: repo_url and repo_branch will be moved to the settings.
    """

    id: uuid.UUID
    name: str
    project_id: uuid.UUID
    repo_url: str
    repo_branch: str
    settings: Dict[str, EnvSettingType]


class Project(BaseModel):
    """
        An inmanta environment.
    """

    id: uuid.UUID
    name: str
    environments: List[Environment]


class EnvironmentSetting(BaseModel):
    """ A class to define a new environment setting.

        :param name: The name of the setting.
        :param type: The type of the value. This type is mainly used for documentation purpose.
        :param default: An optional default value for this setting. When a default is set and the
                        is requested from the database, it will return the default value and also store
                        the default value in the database.
        :param doc: The documentation/help string for this setting
        :param recompile: Trigger a recompile of the model when a setting is updated?
        :param update_model: Update the configuration model (git pull on project and repos)
        :param agent_restart: Restart autostarted agents when this settings is updated.
        :param allowed_values: list of possible values (if type is enum)
    """

    name: str
    type: str
    default: EnvSettingType
    doc: str
    recompile: bool
    update_model: bool
    agent_restart: bool
    allowed_values: Optional[List[EnvSettingType]]


class EnvironmentSettingsReponse(BaseModel):

    settings: Dict[str, EnvSettingType]
    definition: Dict[str, EnvironmentSetting]


class ModelMetadata(BaseModel):
    """ Model metadata
    """

    inmanta_compile_state: const.Compilestate = const.Compilestate.success
    message: str
    type: str
    extra_data: Optional[JsonType]

    class Config:
        fields = {"inmanta_compile_state": {"alias": "inmanta:compile:state"}}


class ModelVersionInfo(BaseModel):
    """ Version information that can be associated with an orchestration model

        :param export_metadata: Metadata associated with this version
        :param model: A serialization of the complete orchestration model
    """

    export_metadata: ModelMetadata
    model: Optional[JsonType]


class Resource(BaseModel):
    environment: uuid.UUID
    model: int
    resource_id: ResourceVersionIdStr
    resource_type: ResourceType
    resource_version_id: ResourceVersionIdStr
    agent: str
    last_deploy: Optional[datetime.datetime]
    attributes: JsonType
    status: const.ResourceState
