import string
import logging
import uuid

from random import SystemRandom
from datetime import datetime
from peewee import *
from sqlalchemy.engine.url import make_url
from urlparse import urlparse

from app import app


logger = logging.getLogger(__name__)


SCHEME_DRIVERS = {
  'mysql': MySQLDatabase,
  'sqlite': SqliteDatabase,
}


def generate_db(config_object):
  db_kwargs = dict(config_object['DB_CONNECTION_ARGS'])
  parsed_url = make_url(config_object['DB_URI'])

  if parsed_url.host:
    db_kwargs['host'] = parsed_url.host
  if parsed_url.port:
    db_kwargs['port'] = parsed_url.port
  if parsed_url.username:
    db_kwargs['user'] = parsed_url.username
  if parsed_url.password:
    db_kwargs['passwd'] = parsed_url.password

  return SCHEME_DRIVERS[parsed_url.drivername](parsed_url.database, **db_kwargs)


db = generate_db(app.config)


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


def uuid_generator():
  return str(uuid.uuid4())


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


class User(BaseModel):
  username = CharField(unique=True, index=True)
  password_hash = CharField(null=True)
  email = CharField(unique=True, index=True,
                    default=random_string_generator(length=64))
  verified = BooleanField(default=False)
  stripe_id = CharField(index=True, null=True)
  organization = BooleanField(default=False, index=True)
  robot = BooleanField(default=False, index=True)
  invoice_email = BooleanField(default=False)


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


class Team(BaseModel):
  name = CharField(index=True)
  organization = ForeignKeyField(User, index=True)
  role = ForeignKeyField(TeamRole)
  description = TextField(default='')

  class Meta:
    database = db
    indexes = (
      # A team name must be unique within an organization
      (('name', 'organization'), True),
    )


class TeamMember(BaseModel):
  user = ForeignKeyField(User, index=True)
  team = ForeignKeyField(Team, index=True)

  class Meta:
    database = db
    indexes = (
      # A user may belong to a team only once
      (('user', 'team'), True),
    )


class LoginService(BaseModel):
  name = CharField(unique=True, index=True)


class FederatedLogin(BaseModel):
  user = ForeignKeyField(User, index=True)
  service = ForeignKeyField(LoginService, index=True)
  service_ident = CharField()

  class Meta:
    database = db
    indexes = (
      # create a unique index on service and the local service id
      (('service', 'service_ident'), True),

      # a user may only have one federated login per service
      (('service', 'user'), True),
    )


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


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

  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):
  team = ForeignKeyField(Team, index=True, null=True)
  user = ForeignKeyField(User, index=True, null=True)
  repository = ForeignKeyField(Repository, index=True)
  role = ForeignKeyField(Role)

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


class PermissionPrototype(BaseModel):
  org = ForeignKeyField(User, index=True, related_name='orgpermissionproto')
  uuid = CharField(default=uuid_generator)
  activating_user = ForeignKeyField(User, index=True, null=True,
                                    related_name='userpermissionproto')
  delegate_user = ForeignKeyField(User, related_name='receivingpermission',
                                  null=True)
  delegate_team = ForeignKeyField(Team, related_name='receivingpermission',
                                  null=True)
  role = ForeignKeyField(Role)

  class Meta:
    database = db
    indexes = (
      (('org', 'activating_user'), False),
    )


class Webhook(BaseModel):
  public_id = CharField(default=random_string_generator(length=64),
                        unique=True, index=True)
  repository = ForeignKeyField(Repository)
  parameters = TextField()


class AccessToken(BaseModel):
  friendly_name = CharField(null=True)
  code = CharField(default=random_string_generator(length=64), unique=True,
                   index=True)
  repository = ForeignKeyField(Repository)
  created = DateTimeField(default=datetime.now)
  role = ForeignKeyField(Role)
  temporary = BooleanField(default=True)


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


class RepositoryBuildTrigger(BaseModel):
  uuid = CharField(default=uuid_generator)
  service = ForeignKeyField(BuildTriggerService, index=True)
  repository = ForeignKeyField(Repository, index=True)
  connected_user = ForeignKeyField(User)
  auth_token = CharField()
  config = TextField(default='{}')
  write_token = ForeignKeyField(AccessToken, null=True)
  pull_robot = ForeignKeyField(User, null=True, related_name='triggerpullrobot')


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


class ImageStorage(BaseModel):
  uuid = CharField(default=uuid_generator)
  checksum = CharField(null=True)
  created = DateTimeField(null=True)
  comment = TextField(null=True)
  command = TextField(null=True)
  image_size = BigIntegerField(null=True)  


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.
  docker_image_id = CharField()
  checksum = CharField(null=True)
  created = DateTimeField(null=True)
  comment = TextField(null=True)
  command = TextField(null=True)
  repository = ForeignKeyField(Repository)
  image_size = BigIntegerField(null=True)

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

  storage = ForeignKeyField(ImageStorage, index=True, null=True)

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


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

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


class RepositoryBuild(BaseModel):
  uuid = CharField(default=uuid_generator, index=True)
  repository = ForeignKeyField(Repository, index=True)
  access_token = ForeignKeyField(AccessToken)
  resource_key = CharField(index=True)
  job_config = TextField()
  phase = CharField(default='waiting')
  started = DateTimeField(default=datetime.now)
  display_name = CharField()
  trigger = ForeignKeyField(RepositoryBuildTrigger, null=True, index=True)
  pull_robot = ForeignKeyField(User, null=True, related_name='buildpullrobot')


class QueueItem(BaseModel):
  queue_name = CharField(index=True, max_length=1024)
  body = TextField()
  available_after = DateTimeField(default=datetime.now, index=True)
  available = BooleanField(default=True, index=True)
  processing_expires = DateTimeField(null=True, index=True)
  retries_remaining = IntegerField(default=5)


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


class LogEntry(BaseModel):
  kind = ForeignKeyField(LogEntryKind, index=True)
  account = ForeignKeyField(User, index=True, related_name='account')
  performer = ForeignKeyField(User, index=True, null=True,
                              related_name='performer')  
  repository = ForeignKeyField(Repository, index=True, null=True)
  access_token = ForeignKeyField(AccessToken, null=True)
  datetime = DateTimeField(default=datetime.now, index=True)
  ip = CharField(null=True)
  metadata_json = TextField(default='{}')


class OAuthApplication(BaseModel):
  client_id = CharField(index=True, default=random_string_generator(length=20))
  client_secret = CharField(default=random_string_generator(length=40))
  redirect_uri = CharField()
  application_uri = CharField()
  organization = ForeignKeyField(User)

  name = CharField()
  description = TextField(default='')
  gravatar_email = CharField(null=True)


class OAuthAuthorizationCode(BaseModel):
  application = ForeignKeyField(OAuthApplication)
  code = CharField(index=True)
  scope = CharField()
  data = TextField()  # Context for the code, such as the user


class OAuthAccessToken(BaseModel):
  uuid = CharField(default=uuid_generator, index=True)
  application = ForeignKeyField(OAuthApplication)
  authorized_user = ForeignKeyField(User)
  scope = CharField()
  access_token = CharField(index=True)
  token_type = CharField(default='Bearer')
  expires_at = DateTimeField()
  refresh_token = CharField(index=True, null=True)
  data = TextField()  # This is context for which this token was generated, such as the user


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


class Notification(BaseModel):
  uuid = CharField(default=uuid_generator, index=True)
  kind = ForeignKeyField(NotificationKind, index=True)
  target = ForeignKeyField(User, index=True)
  metadata_json = TextField(default='{}')
  created = DateTimeField(default=datetime.now, index=True)


all_models = [User, Repository, Image, AccessToken, Role, RepositoryPermission, Visibility,
              RepositoryTag, EmailConfirmation, FederatedLogin, LoginService, QueueItem,
              RepositoryBuild, Team, TeamMember, TeamRole, Webhook, LogEntryKind, LogEntry,
              PermissionPrototype, ImageStorage, BuildTriggerService, RepositoryBuildTrigger,
              OAuthApplication, OAuthAuthorizationCode, OAuthAccessToken, NotificationKind,
              Notification]