import string
from random import SystemRandom

from peewee import *
from peewee import create_model_tables

from datetime import datetime

db = SqliteDatabase('test.db', threadlocals=True)


class BaseModel(Model):
  class Meta:
    database = db


class User(BaseModel):
  username = CharField(unique=True, index=True)
  password_hash = CharField()
  email = CharField(unique=True, index=True)
  verified = BooleanField(default=False)


class Visibility(BaseModel):
  name = CharField(index=True)


class Repository(BaseModel):
  namespace = CharField()
  name = CharField()
  visibility = ForeignKeyField(Visibility)
  description = CharField(null=True)

  class Meta:
    database = db
    indexes = (
      # create a unique index on namespace and name
      (('namespace', 'name'), True),
    )


class Role(BaseModel):
  name = CharField(index=True)


class RepositoryPermission(BaseModel):
  user = ForeignKeyField(User, index=True)
  repository = ForeignKeyField(Repository, index=True)
  role = ForeignKeyField(Role)

  class Meta:
    database = db
    indexes = (
      (('user', 'repository'), True),
    )


def random_string_generator(length=16):
  def random_string():
    random = SystemRandom()
    return ''.join([random.choice(string.ascii_uppercase + string.digits)
                   for x in range(length)])
  return random_string


class AccessToken(BaseModel):
  code = CharField(default=random_string_generator(), unique=True, index=True)
  user = ForeignKeyField(User)
  repository = ForeignKeyField(Repository)
  created = DateTimeField(default=datetime.now)


class EmailConfirmation(BaseModel):
  code = CharField(default=random_string_generator(), unique=True, index=True)
  user = ForeignKeyField(User)
  pw_reset = BooleanField(default=False)
  email_confirm = BooleanField(default=False)
  created = DateTimeField(default=datetime.now)


class Image(BaseModel):
  # This class is intentionally denormalized. Even though images are supposed
  # to be globally unique we can't treat them as such for permissions and
  # security reasons. So rather than Repository <-> Image being many to many
  # each image now belongs to exactly one repository.
  image_id = CharField()
  checksum = CharField(null=True)
  created = DateTimeField(null=True)
  comment = CharField(null=True)
  repository = ForeignKeyField(Repository)

  # '/' separated list of ancestory ids, e.g. /1/2/6/7/10/
  ancestors = CharField(index=True, default='/', max_length=65535)

  class Meta:
    database = db
    indexes = (
      # we don't really want duplicates
      (('repository', 'image_id'), True),
    )


class RepositoryTag(BaseModel):
  name = CharField()
  image = ForeignKeyField(Image)
  repository = ForeignKeyField(Repository)

  class Meta:
    database = db
    indexes = (
      (('repository', 'name'), True),
    )


def initialize_db():
  create_model_tables([User, Repository, Image, AccessToken, Role,
                       RepositoryPermission, Visibility, RepositoryTag,
                       EmailConfirmation])
  Role.create(name='admin')
  Role.create(name='write')
  Role.create(name='read')
  Visibility.create(name='public')
  Visibility.create(name='private')


if __name__ == '__main__':
  initialize_db()