# Copyright (C) 2023 Cochise Ruhulessin
#
# All rights reserved. No warranty, explicit or implicit, provided. In
# no event shall the author(s) be liable for any claim or damages.
#
# 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.
import datetime
import uuid
from typing import Any

import pydantic

from .resourcename import ResourceName


class ObjectMeta(pydantic.BaseModel):
    model_config = {'populate_by_name': True}

    annotations: dict[str, Any] = pydantic.Field(
        default_factory=dict,
        title="Annotations",
        description=(
            "Annotations is an unstructured key value map stored with "
            "a resource that may be set by external tools to store "
            "and retrieve arbitrary metadata. They are not queryable and "
            "should be preserved when modifying objects."
        )
    )
    
    created: datetime.datetime | None = pydantic.Field(
        default_factory=lambda: datetime.datetime.now(datetime.timezone.utc),
        title="Created",
        description=(
            "`created` is a timestamp representing the server time "
            "when this object was created. It is not guaranteed to be set in "
            "happens-before order across separate operations. Clients may not "
            "set this value. It is represented in RFC3339 form and is in UTC. "
            "Populated by the system. Read-only. Null for lists."
        ),
        frozen=True
    )
    
    deleted: datetime.datetime | None = pydantic.Field(
        default=None,
        title="Deleted",
        description=(
            "The `deleted` field is an RFC 3339 date and time "
            "at which this resource will be deleted. This field "
            "is set by the server when a graceful deletion is "
            "requested by the user, and is not directly settable "
            "by a client. The resource is expected to be deleted "
            "(no longer visible from resource lists, and not "
            "reachable by name) after the time in this field. "
            "Once set, this value may not be unset or be set "
            "further into the future, although it may be shortened "
            "or the resource may be deleted prior to this time."
        ),
    )
    
    updated: datetime.datetime | None = pydantic.Field(
        default_factory=lambda: datetime.datetime.now(datetime.timezone.utc),
        title="Updated",
        description=(
            "`CreationTimestamp` is a timestamp representing the server time "
            "when this object was updated. Clients may not "
            "set this value. It is represented in RFC3339 form and is in UTC. "
            "Populated by the system. Read-only. Null for lists."
        )
    )

    generation: int = pydantic.Field(
        default=1,
        title="Generation",
        description=(
            "A sequence number representing a specific generation "
            "of the state. Populated by the system. Read-only."
        )
    )

    labels: dict[str, str | None] = pydantic.Field(
        default_factory=dict,
        title="Labels",
        description=(
            "Map of string keys and values that can be used to organize and "
            "categorize (scope and select) objects."
        )
    )

    name: ResourceName | str = pydantic.Field(
        default=...,
        title="Name",
        description=(
            "Name must be unique within a namespace. Is required when creating "
            "resources, although some resources may allow a client to request "
            "the generation of an appropriate name automatically. Name is "
            "primarily intended for creation idempotence and configuration "
            "definition. Cannot be updated."
        ),
        frozen=True
    )

    namespace: str = pydantic.Field(
        default='default',
        title="Namespace",
        description=(
            "Namespace defines the space within which each name must "
            "be unique. An empty namespace is equivalent to the `default` "
            "namespace, but `default` is the canonical representation. "
            "Not all objects are required to be scoped to a namespace - "
            "the value of this field for those objects will be empty. "
            "Must be a DNS_LABEL. Cannot be updated."
        )
    )

    tags: set[str] = pydantic.Field(
        default_factory=set
    )

    uid: str = pydantic.Field(
        default_factory=lambda: str(uuid.uuid4()),
        title="UID",
        description=(
            "UID is the unique in time and space value for this object. "
            "It is typically generated by the server on successful creation "
            "of a resource and is not allowed to change on PUT operations. "
            "Populated by the system. Read-only."
        ),
        frozen=True
    )

    @pydantic.field_validator('labels', mode='before')
    def preprocess_labels(cls, values: dict[str, Any] | Any):
        if isinstance(values, dict):
            for label, value in list(values.items()):
                if isinstance(value, str):
                    continue
                if isinstance(value, bool) and value:
                    values[label] = 'true'
                    continue
                if isinstance(value, bool) and not value:
                    values[label] = 'false'
                    continue
                if isinstance(value, int):
                    values[label] = str(value)
                    continue
                if value is None:
                    values[label] = 'null'
                    continue
        return values

    def delete(self) -> None:
        if self.deleted is not None:
            raise ValueError
        self.deleted = datetime.datetime.now(datetime.timezone.utc)
        self.updated = self.deleted

    def merge(self, metadata: 'ObjectMeta') -> None:
        """Merge the annotations, labels and tags of the new
        object into this object.
        """
        self.annotations = {**self.annotations, **metadata.annotations}
        self.labels = {**self.labels, **metadata.labels}
        self.tags.update(metadata.tags)

    def is_labeled(self, names: list[str]) -> bool:
        return all([
            self.labels.get(name) not in {None, 'null'}
            for name in names
        ])

    def set_label(self, name: str, value: Any) -> Any | None:
        if isinstance(value, bool) and value:
            value = 'true'
        if isinstance(value, bool) and not value:
            value = 'false'
        if isinstance(value, int):
            value = str(value)
        if value is None:
            value = 'null'
        if not isinstance(value, str):
            raise TypeError(f'Can not set label of type {type(value).__name__}')
        self.labels[name] = value
        return value