# AUTOGENERATED! DO NOT EDIT! File to edit: nbs/pynamodb.ipynb (unless otherwise specified).

__all__ = [
    "PynamoDBSavingError",
    "PynamoDBSchemaValidationError",
    "SchemaAttribute",
    "DataclassJsonAttribute",
    "extraDocsString",
    "SuperModel",
    "createData",
    "getData",
    "updateData",
]

# Cell
from pynamodb.attributes import Attribute, UnicodeAttribute, NumberAttribute
from typing import Any, Optional, Type, TypeVar
from enum import Enum
import requests, dpath, yaml, jsonschema, json, os, pynamodb, pytest
from pynamodb.models import Model
from datetime import datetime, timezone
from awsSchema.apigateway import Event, Response
from jsonschema import ValidationError
from typing import Optional
from .schema import validateUrl
from beartype import beartype


# Cell
class PynamoDBSavingError(Exception):
    pass


class PynamoDBSchemaValidationError(Exception):
    pass


# Cell
class SchemaAttribute(Attribute):
    attr_type = pynamodb.constants.STRING

    def __init__(
        self,
        schemaUrl: str,
        path: str = "/",
        isYaml=True,
        headers={"Cache-Control": "no-cache"},
        envName="SCHEMA_ATTRIBUTE",
        **kwargs: Any,
    ) -> None:
        """
        schemaUrl:str,
        path:str = '/',
        isYaml=True,  :yaml::Bool:: whether the schema is in yaml or json
        headers={'Cache-Control': 'no-cache'},
        :path::str:: the path of the object of interest in schema, if the schema is at root then '/'
        envName::str:: the name of schema to save to the environment
        """
        super().__init__(**kwargs)
        try:
            if isYaml:  # yaml schema
                schema: dict = yaml.load(
                    requests.get(schemaUrl, headers=headers).text,
                    Loader=yaml.FullLoader,
                )
            else:  # probably json
                schema: dict = requests.get(schemaUrl, headers).json()
        except Exception as e:
            print(f"error parsing schema {e}")
            schema: dict = {}

        self.schema = dpath.get(schema, path)  # get to the path in schema
        os.environ[envName] = json.dumps(self.schema)

    def deserialize(self, value: str) -> dict:
        return json.loads(value)

    def serialize(self, value: dict) -> str:
        res = jsonschema.validate(value, self.schema)
        return json.dumps(value)


# Cell
class DataclassJsonAttribute(Attribute):
    attr_type = pynamodb.constants.STRING

    def __init__(self, customDataClass: "Dataclass", **kwargs: Any) -> None:
        """
        check dataclass before saving
        """
        super().__init__(**kwargs)
        self.schema = customDataClass

    def deserialize(self, value: str) -> dict:
        return json.loads(value)

    def serialize(self, value: dict) -> str:
        res = self.schema.from_dict(value)
        return json.dumps(value)


# Cell
extraDocsString = """
  def pullOutKeys(self):
    self.id_ = self.data['id']
  def update(self, inputDict:dict):
    self.data.update(inputDict)
  def fromDict(cls, inputDict:dict):
    return cls(data = inputDict)
  def toDict(self):
    return self.data

"""


# Cell
class SuperModel(Model):
    f"""
  a model intended to use as a standard single json object saving
  please override functions including
  - fromDict
  - toDict
  - pullOutKeys
  - update
  override as necessary for example
  {extraDocsString}
  """
    # id_ = UnicodeAttribute(hash_key=True)
    data = SchemaAttribute(
        schemaUrl="https://gist.githubusercontent.com/thanakijwanavit/e2720d091ae0cef710a49b57c0c9cd4c/raw/ed2d322eac4900ee0f95b431d0f9067a40f3e0f0/squirrelOpenApiV0.0.3.yaml",
        null=True,
    )
    lastEdited = NumberAttribute()
    creationTime = NumberAttribute()

    def __repr__(self):
        return json.dumps(vars(self)["attribute_values"])

    ## query override
    @classmethod
    def queryId(cls, hash_key, **kwargs):
        """
        just like query but make resul
        """
        r = cls.query(hash_key, **kwargs)
        res = []
        for i in r:
            i.pullOutKeys()
            res.append(i)
        return iter(res)

    ## moving to and from dict
    @classmethod
    def fromDict(cls, inputDict: dict):
        """
        turn dict into class
        note that this assume data as the only column, please override if required
        """
        return cls(data=inputDict)

    def toDict(self):
        """
        turn class into a dictionary
        note that this is set to return self.data by default, please override if needed
        """
        return self.data

    #### saving ####
    def pullOutKeys(self):
        """
        update the keys with data: please override this function by pulling out keys

        for example
        self.orderId = self.data['orderId']
        self.ownerId = self.data['ownerId']
        self.basketId = self.data['basketId']
        """
        print("please dont foreget to override the pullOutKeys function if needed")
        self.id_ = self.data["id"]

    def recordTime(self, tz=timezone.utc):
        """record last edited and creation time"""
        self.lastEdited = datetime.now(tz=tz).timestamp()  # record last edited
        if not self.creationTime:  # record creation time
            self.creationTime = datetime.now(tz=tz).timestamp()

    def save(self):
        """
        please override pullOutKeys function
        see docs
        this function performs following before saving the record
          1. self.recordTime()
          2. self.pullOutKeys()
        """
        self.recordTime()
        self.pullOutKeys()

        try:
            super().save()
            return self
        except ValidationError as e:
            raise PynamoDBSchemaValidationError(f"failed validation \n {e}")
        except Exception as e:
            raise PynamoDBSavingError(f"error saving id  {e}")

    def update(self, inputDict: dict):
        """
        update with dictionary input
        please override as necessary
        """
        self.data.update(inputDict)


# Cell
def createData(
    event: dict,
    hashKeyName: str,
    mainClass: Model,
    schemaUrl: Optional[str] = None,
    schemaFormat: str = "yaml",
    *args,
):
    """
    create a new row of data
    """
    # parse output
    query: dict = Event.parseBody(event)

    # check schema if provided
    if schemaUrl:
        try:
            validateUrl(schemaUrl, query, format_=schemaFormat)
        except ValidationError as e:
            return Response.returnSuccess(f"{e}")

    # check for key
    if hashKeyName not in query:
        return Response.returnError(message=f"missing {hashKeyName}")

    # check if object exist
    if next(mainClass.query(query[hashKeyName]), None):
        return Response.returnError(message=f"item with the same hash key exists")

    # make pynamodb object
    item: mainClass = mainClass.fromDict(query)

    # try to save
    try:
        item.save()
        return Response.returnSuccess(body=item.toDict())

    except ValidationError as e:  # error validation handle
        return Response.returnError(f"validation error \n {e}")

    except Exception as e:  # error handle
        return Response.returnError(f"unknown error \n {e} \n errorString())")


# Cell
from awsSchema.apigateway import Event, Response
from jsonschema import ValidationError
from typing import Optional
from .schema import validateUrl


def getData(hashKeyName: str, mainClass: Model):
    """
    create a new basket
    """
    # get data
    try:
        r: Optional[Model] = next(mainClass.query(hashKeyName), None)
    except Exception as e:
        return Response.returnError(f"failed to query with error {e}")

    # product not found
    if not r:
        return Response.returnError(f"not found")
    # success
    else:
        return Response.returnSuccess(r.toDict())


# Cell
def updateData(
    event: dict,
    hashKeyName: str,
    mainClass: Model,
    schemaUrl: Optional[str] = None,
    schemaFormat: str = "yaml",
    *args,
):
    """
    updating data based on the new input

    event:dict: object gathered from apiGatewayProxy
    hashKeyName:str: the name of the hash key
    mainClass:Model: pynamodb model class for this object
    schemaUrl:Optional[str]: url of the input schema for validation
    schemaFormat:Enum['yaml', 'json']
    """
    # parse output
    query: dict = Event.parseBody(event)

    # check schema if provided
    if schemaUrl:
        try:
            validateUrl(schemaUrl, query, format_=schemaFormat)
        except ValidationError as e:
            return Response.returnSuccess(f"{e}")

    # check for key
    if hashKeyName not in query:
        return Response.returnError(message=f"missing {hashKeyName}")

    # check if object exist
    item: mainClass = next(mainClass.query(query[hashKeyName]), None)
    if not item:
        return Response.returnError(message=f"item with the same hash key doesnt exist")

    # update the data
    item.update(query)

    # try to save
    try:
        item.save()
        return Response.returnSuccess(body=item.toDict())

    except ValidationError as e:  # error validation handle
        return Response.returnError(f"validation error \n {e}")

    except Exception as e:  # error handle
        return Response.returnError(f"unknown error \n {e} \n errorString())")
