# we will use prov-o to represent the provenance of the neat graph store
# basically tracking changes that occur in the graph store
# prov-o use concepts of Agent, Activity and Entity to represent provenance
# where in case of neat we have:
# Agent: triples extractors, graph enhancers, contextualizers, etc.
# Activity: write triple, add connection, etc.
# Entity: neat graph store


import uuid
from collections import UserList
from collections.abc import Sequence
from dataclasses import dataclass
from datetime import datetime
from typing import TypeVar

from rdflib import PROV, RDF, Literal, URIRef

from cognite.neat.constants import DEFAULT_NAMESPACE


@dataclass(frozen=True)
class Agent:
    id_: URIRef = DEFAULT_NAMESPACE.agent
    acted_on_behalf_of: str = "NEAT"

    def as_triples(self):
        return [
            (self.id_, RDF.type, PROV[type(self).__name__]),
            (self.id_, PROV.actedOnBehalfOf, self.acted_on_behalf_of),
        ]


@dataclass(frozen=True)
class Activity:
    was_associated_with: Agent
    ended_at_time: datetime
    started_at_time: datetime
    used: str  # this would be set to for example Extractor, Enhancer, Contextualizer, etc.
    id_: URIRef = DEFAULT_NAMESPACE[f"activity-{uuid.uuid4()}"]

    def as_triples(self):
        return [
            (self.id_, RDF.type, PROV[type(self).__name__]),
            (self.id_, PROV.wasAssociatedWith, self.was_associated_with.id_),
            (self.id_, PROV.startedAtTime, Literal(self.started_at_time)),
            (self.id_, PROV.endedAtTime, Literal(self.ended_at_time)),
            (self.id_, PROV.used, self.used),
        ]


@dataclass(frozen=True)
class Entity:
    was_generated_by: Activity
    was_attributed_to: Agent
    id_: URIRef = DEFAULT_NAMESPACE["graph-store"]

    def as_triples(self):
        return [
            (self.id_, RDF.type, PROV[type(self).__name__]),
            (self.id_, PROV.wasGeneratedBy, self.was_generated_by.id_),
            (self.id_, PROV.wasAttributedTo, self.was_attributed_to.id_),
        ]


@dataclass(frozen=True)
class Change:
    agent: Agent
    activity: Activity
    entity: Entity
    description: str

    def as_triples(self):
        return self.agent.as_triples() + self.activity.as_triples() + self.entity.as_triples()

    @classmethod
    def record(cls, activity: str, start: datetime, end: datetime, description: str):
        """User friendly method to record a change that occurred in the graph store."""
        agent = Agent()
        activity = Activity(used=activity, was_associated_with=agent, started_at_time=start, ended_at_time=end)
        entity = Entity(was_generated_by=activity, was_attributed_to=agent)
        return cls(agent, activity, entity, description)


T_Change = TypeVar("T_Change", bound=Change)


class Provenance(UserList[T_Change]):
    def __init__(self, changes: Sequence[T_Change] | None = None):
        super().__init__(changes or [])

    def activity_took_place(self, activity: str) -> bool:
        return any(change.activity.used == activity for change in self)

    def __delitem__(self, *args, **kwargs):
        raise TypeError("Cannot delete change from provenance")

    def __setitem__(self, *args, **kwargs):
        raise TypeError("Cannot modify change from provenance")
