# (c) cavaliba.com - data - models.py

import uuid

from django.db import models
from django.utils.translation import gettext_lazy as _

# ----------------------------------------------------------------------------------
# DataClass (Schemas)
# ----------------------------------------------------------------------------------


class DataClassManager(models.Manager):
    def get_by_natural_key(self, keyname):
        return self.get(keyname=keyname)


class DataClass(models.Model):
    keyname = models.SlugField(max_length=128, null=False, blank=False, unique=True)
    displayname = models.CharField(max_length=500, null=True, blank=True)
    is_enabled = models.BooleanField(default=True)

    page = models.CharField(max_length=255, null=True, blank=True)
    order = models.IntegerField(default=100)

    # V3.19 - YAML (in YAML v3.20: _options: )
    options = models.TextField(max_length=5000, null=True, blank=True, default="")

    #  CRUD permissions per class ; override global built-in ; apply to instances in that class
    p_create = models.CharField(max_length=256, null=True, blank=True)
    p_read = models.CharField(max_length=256, null=True, blank=True)
    p_update = models.CharField(max_length=256, null=True, blank=True)
    p_delete = models.CharField(max_length=256, null=True, blank=True)
    # no override  / all perms
    p_admin = models.CharField(max_length=256, null=True, blank=True)

    # UI ajax or not
    is_bigset = models.BooleanField(default=False)

    #  TODO: deprecate ; not a schema related info
    count_estimation = models.IntegerField(default=0)

    class Meta:
        # db_table = "{{ app_name}}_category"
        db_table = "data_schema"
        ordering = ["keyname"]
        verbose_name = "Data Schema"
        verbose_name_plural = "Data Schemas"

    def __str__(self):
        return f"{self.keyname}"

    def natural_key(self):
        return self.keyname


# ----------------------------------------------------------------------------------
# DataSchema FIELDS
# ----------------------------------------------------------------------------------

# default to unknown if not found
# *MUST* have :
# - a corresponding field_XXXX.py
# - a  _XXXX.html template
# - an entry in data.py

FIELD_FORMAT_CHOICE = (
    ("string", "string"),
    ("int", "int"),
    ("float", "float"),
    ("boolean", "boolean"),
    ("ipv4", "ipv4"),
    ("date", "date"),
    ("datetime", "datetime"),
    ("time", "time"),
    ("schema", "schema"),
    ("user", "user"),
    ("group", "group"),
    ("role", "role"),
    ("text", "text"),
    ("enumerate", "enumerate"),
    ("external", "external"),
    ("file", "file"),
    ("password", "password"),  # V3.28
    ("permission", "permission"),  # v4.0
)


class DataSchemaManager(models.Manager):
    def get_by_natural_key(self, keyname, classname):
        return self.get(keyname=keyname, classname=classname)


class DataSchema(models.Model):
    classname = models.SlugField("Schema", max_length=128, null=False, blank=False)
    keyname = models.SlugField(max_length=128, null=False, blank=False)
    displayname = models.CharField(max_length=500, null=True, blank=True)
    description = models.CharField(_("Description"), max_length=500, blank=True)
    is_enabled = models.BooleanField(default=True)

    dataformat = models.CharField(choices=FIELD_FORMAT_CHOICE, max_length=32, default="string")
    dataformat_ext = models.CharField(max_length=500, null=True, blank=True)
    default_value = models.CharField(max_length=500, null=True, blank=True, default="")

    page = models.CharField(max_length=255, null=True, blank=True)
    order = models.IntegerField(default=100)

    cardinal_min = models.IntegerField(default=0)
    cardinal_max = models.IntegerField(default=1)

    #  NEXT - CRUD permissions at field level
    ###p_create = models.CharField(max_length=256,  null=True, blank=True)
    # p_read = models.CharField(max_length=256,  null=True, blank=True)
    # p_update = models.CharField(max_length=256,  null=True, blank=True)
    ###p_delete = models.CharField(max_length=256,  null=True, blank=True)

    class Meta:
        db_table = "data_field"
        unique_together = ["classname", "keyname"]
        ordering = ["classname", "keyname", "order"]
        verbose_name = "Data Field"
        verbose_name_plural = "Data Fields"

    def __str__(self):
        return f"{self.classname}:{self.keyname}({self.dataformat}/{self.dataformat_ext})"

    def natural_key(self):
        return (self.classname, self.keyname)


# ----------------------------------------------------------------------------------
# DataInstance
# ----------------------------------------------------------------------------------


class DataInstanceManager(models.Manager):
    def get_by_natural_key(self, classname, keyname):
        return self.get(classname=classname, keyname=keyname)


class DataInstance(models.Model):
    classname = models.SlugField("Schema", max_length=128, null=True, blank=True)
    keyname = models.CharField(max_length=256, null=False, blank=False)
    displayname = models.CharField(max_length=500, null=True, blank=True)
    is_enabled = models.BooleanField(default=True)

    data_json = models.TextField(null=True, blank=True)

    #  CRUD permissions per instance - override per-clas / built-in global
    p_read = models.CharField(max_length=256, null=True, blank=True)
    p_update = models.CharField(max_length=256, null=True, blank=True)
    p_delete = models.CharField(max_length=256, null=True, blank=True)

    last_update = models.DateTimeField(_("Last update"), auto_now_add=False, blank=True, null=True)

    class Meta:
        db_table = "data_instance"
        indexes = [
            models.Index(fields=["keyname"]),
            models.Index(fields=["classname"]),
            models.Index(fields=["classname", "keyname"]),
            models.Index(fields=["is_enabled"]),
        ]
        unique_together = ["classname", "keyname"]
        ordering = ["classname", "keyname"]
        verbose_name = "Data Instance"
        verbose_name_plural = "Data Instances"

    def __str__(self):
        if self.displayname:
            return f"{self.displayname}"
        else:
            return f"{self.keyname}"

    def natural_key(self):
        return (self.classname, self.keyname)


# ----------------------------------------------------------------------------------
# DataRegistry - v3.16
# ----------------------------------------------------------------------------------
# Simple Key/Value storage


class DataRegistry(models.Model):
    key = models.CharField(max_length=255, null=False, blank=False, unique=True)
    value = models.TextField(max_length=50000, null=True, blank=True)

    # NEXT: date ; created, updated, expire

    class Meta:
        db_table = "data_registry"
        ordering = ["key"]
        verbose_name = "Registry entry"
        verbose_name_plural = "Registry entries"

    def __str__(self):
        return f"{self.key}"


# ----------------------------------------------------------------------------------
# DataEAV  - v3.18
# ----------------------------------------------------------------------------------


class DataEAV(models.Model):
    # instance id (DB pk of instance)
    iid = models.BigIntegerField(default=0)
    #  instance:
    classname = models.CharField(max_length=128, null=False, blank=False)
    keyname = models.CharField(max_length=128, null=False, blank=False)
    displayname = models.CharField(max_length=500, null=True, blank=True)
    is_enabled = models.BooleanField(default=True)

    fieldname = models.CharField(max_length=500, null=True, blank=True)
    format = models.CharField(max_length=512, null=True, blank=True)
    value = models.CharField(max_length=1024, null=True, blank=True)

    p_read = models.CharField(max_length=256, null=True, blank=True)
    last_update = models.DateTimeField(auto_now_add=False, blank=True, null=True)

    class Meta:
        db_table = "data_cache"
        verbose_name = "Data Cache"
        verbose_name_plural = "Data Cache"
        indexes = [
            models.Index(fields=["iid"]),
            models.Index(fields=["classname"]),
            models.Index(fields=["keyname"]),
            models.Index(fields=["fieldname"]),
            models.Index(fields=["format"]),
            models.Index(fields=["value"]),
            models.Index(fields=["last_update"]),
            models.Index(fields=["classname", "keyname"]),
            models.Index(fields=["classname", "keyname", "fieldname"]),
        ]

    def __str__(self):
        return f"{self.classname}:{self.keyname}:{self.fieldname}"


# ----------------------------------------------------------------------------------
# DataFile - v3.19
# ----------------------------------------------------------------------------------


class DataFileManager(models.Manager):
    def get_by_natural_key(self, fileid):
        return self.get(fileid=fileid)


class DataFile(models.Model):
    fileid = models.CharField(max_length=128, null=False, blank=False, unique=True)

    # real filename : myfile.txt
    filename = models.CharField(max_length=256, null=False, blank=False)

    displayname = models.CharField(max_length=500, null=True, blank=True)

    # store/path: /a/b/c/d/myfile.txt ; s3://a/b/myfile.txt  ;  relative to Root FileStore conf
    filepath = models.CharField(max_length=128, null=True, blank=True)

    # future / options
    # fileopts = models.CharField(max_length=512, null=True, blank=True)
    # is_versioned = models.BooleanField(default=False)

    p_read = models.CharField(max_length=256, null=True, blank=True)
    p_write = models.CharField(max_length=256, null=True, blank=True)

    size = models.BigIntegerField(null=True, blank=True)
    hash = models.CharField(max_length=128, null=True, blank=True)

    is_encrypted = models.BooleanField(default=False)
    is_enabled = models.BooleanField(default=True)

    not_after = models.DateTimeField(auto_now_add=False, blank=True, null=True)
    last_update = models.DateTimeField(auto_now_add=True, blank=True, null=True)

    class Meta:
        db_table = "data_file"
        verbose_name = "Data File"
        verbose_name_plural = "Data Files"
        indexes = [
            models.Index(fields=["is_enabled"]),
            models.Index(fields=["last_update"]),
            models.Index(fields=["not_after"]),
        ]

    def __str__(self):
        return f"{self.displayname}"

    def natural_key(self):
        return self.fileid


# ----------------------------------------------------------------------------------
# DataRevision - v3.23
# ----------------------------------------------------------------------------------


class DataRevision(models.Model):
    date = models.DateTimeField(auto_now_add=True, blank=False, null=False)
    classname = models.SlugField("Schema", max_length=128, null=False, blank=False)
    keyname = models.CharField(max_length=256, null=False, blank=False)
    username = models.CharField(max_length=256, null=True, blank=True)
    displayname = models.CharField(max_length=256, null=True, blank=True)
    action = models.CharField(max_length=256, null=True, blank=True)
    # next: data_diff old/new

    class Meta:
        db_table = "data_revision"
        indexes = [
            models.Index(fields=["date"]),
            models.Index(fields=["classname", "keyname"]),
        ]
        verbose_name = "Data Revision"
        verbose_name_plural = "Data Revisions"

    def __str__(self):
        return f"{self.date} {self.classname} {self.keyname} {self.displayname} {self.action}"


# ----------------------------------------------------------------------------------
# DataTask - async long-running task tracking
# v3.33
# ----------------------------------------------------------------------------------

DATATASK_STATE_CHOICES = [
    ("QUEUED", "Queued"),
    ("RUNNING", "Running"),
    ("DONE", "Done"),
    ("FAILED", "Failed"),
    ("ABORTED", "Aborted"),
]

DATATASK_OWNER_TYPE_CHOICES = [
    ("user", "User"),
    ("api", "API"),
    ("auto", "Auto"),
]


class DataTask(models.Model):
    handle = models.UUIDField(default=uuid.uuid4, unique=True, db_index=True)
    owner_type = models.CharField(
        max_length=16, choices=DATATASK_OWNER_TYPE_CHOICES, default="user"
    )
    owner_id = models.CharField(max_length=128, db_index=True)
    name = models.CharField(max_length=256)
    singleton = models.CharField(max_length=64, blank=True, null=True, db_index=True)
    state = models.CharField(
        max_length=16, choices=DATATASK_STATE_CHOICES, default="QUEUED", db_index=True
    )
    params = models.JSONField(blank=True, null=True)
    progress = models.JSONField(blank=True, null=True)
    output = models.JSONField(blank=True, null=True)
    attachment = models.JSONField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    finished_at = models.DateTimeField(blank=True, null=True)
    duration = models.FloatField(blank=True, null=True)

    class Meta:
        db_table = "data_task"
        ordering = ["-created_at"]
        verbose_name = "Data Task"
        verbose_name_plural = "Data Tasks"

    def __str__(self):
        return f"{self.name} [{self.state}] {self.handle}"
