from app import app, db
import git
from datetime import datetime
from enum import Enum
from PIL import Image
from cairosvg import svg2png

__all__ = [
    "RepoAccess",
    "RepoFavourite",
    "Repo",
    "UserFollow",
    "UserNotification",
    "User",
    "Notification",
    "PostVote",
    "Post",
    "Commit",
]

with app.app_context():
    class RepoAccess(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
        accessLevel = db.Column(db.SmallInteger(), nullable=False)  # 0 read-only, 1 read-write, 2 admin

        user = db.relationship("User", back_populates="repoAccess")
        repo = db.relationship("Repo", back_populates="repoAccess")

        __table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc"),)

        def __init__(self, user, repo, level):
            self.userUsername = user.username
            self.repoRoute = repo.route
            self.accessLevel = level


    class RepoFavourite(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)

        user = db.relationship("User", back_populates="favourites")
        repo = db.relationship("Repo", back_populates="favourites")

        __table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc1"),)

        def __init__(self, user, repo):
            self.userUsername = user.username
            self.repoRoute = repo.route


    class PostVote(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        postIdentifier = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=False)
        voteScore = db.Column(db.SmallInteger(), nullable=False)

        user = db.relationship("User", back_populates="votes")
        post = db.relationship("Post", back_populates="votes")

        __table_args__ = (db.UniqueConstraint("userUsername", "postIdentifier", name="_user_post_uc"),)

        def __init__(self, user, post, score):
            self.userUsername = user.username
            self.postIdentifier = post.identifier
            self.voteScore = score


    class User(db.Model):
        username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True)
        displayName = db.Column(db.Unicode(128), unique=False, nullable=True)
        bio = db.Column(db.Unicode(512), unique=False, nullable=True)
        passwordHashed = db.Column(db.String(60), nullable=False)
        email = db.Column(db.String(254), nullable=True)
        company = db.Column(db.Unicode(64), nullable=True)
        companyURL = db.Column(db.String(256), nullable=True)
        URL = db.Column(db.String(256), nullable=True)
        showMail = db.Column(db.Boolean, default=False, nullable=False)
        location = db.Column(db.Unicode(64), nullable=True)
        creationDate = db.Column(db.DateTime, default=datetime.utcnow)

        repositories = db.relationship("Repo", back_populates="owner")
        followers = db.relationship("UserFollow", back_populates="followed", foreign_keys="[UserFollow.followedUsername]")
        follows = db.relationship("UserFollow", back_populates="follower", foreign_keys="[UserFollow.followerUsername]")
        repoAccess = db.relationship("RepoAccess", back_populates="user")
        votes = db.relationship("PostVote", back_populates="user")
        favourites = db.relationship("RepoFavourite", back_populates="user")

        commits = db.relationship("Commit", back_populates="owner")
        posts = db.relationship("Post", back_populates="owner")
        notifications = db.relationship("UserNotification", back_populates="user")

        def __init__(self, username, password, email=None, displayName=None):
            self.username = username
            self.passwordHashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8")
            self.email = email
            self.displayName = displayName

            # Create the user's directory
            if not os.path.exists(os.path.join(config.REPOS_PATH, username)):
                os.makedirs(os.path.join(config.REPOS_PATH, username))
            if not os.path.exists(os.path.join(config.USERDATA_PATH, username)):
                os.makedirs(os.path.join(config.USERDATA_PATH, username))

            avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH))
            if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"):
                cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName),
                                 write_to="/tmp/roundabout-avatar.png")
                avatar = Image.open("/tmp/roundabout-avatar.png")
            else:
                avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName))
            avatar.thumbnail(config.AVATAR_SIZE)
            avatar.save(os.path.join(config.USERDATA_PATH, username, "avatar.png"))


    class Repo(db.Model):
        route = db.Column(db.String(98), unique=True, nullable=False, primary_key=True)
        ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        name = db.Column(db.String(64), nullable=False)
        owner = db.relationship("User", back_populates="repositories")
        visibility = db.Column(db.SmallInteger(), nullable=False)
        info = db.Column(db.Unicode(512), nullable=True)
        URL = db.Column(db.String(256), nullable=True)
        creationDate = db.Column(db.DateTime, default=datetime.utcnow)

        defaultBranch = db.Column(db.String(64), nullable=True, default="")

        commits = db.relationship("Commit", back_populates="repo")
        posts = db.relationship("Post", back_populates="repo")
        repoAccess = db.relationship("RepoAccess", back_populates="repo")
        favourites = db.relationship("RepoFavourite", back_populates="repo")

        lastPostID = db.Column(db.Integer, nullable=False, default=0)

        def __init__(self, owner, name, visibility):
            self.route = f"/{owner.username}/{name}"
            self.name = name
            self.ownerName = owner.username
            self.owner = owner
            self.visibility = visibility

            # Add the owner as an admin
            repoAccess = RepoAccess(owner, self, 2)
            db.session.add(repoAccess)


    class Commit(db.Model):
        identifier = db.Column(db.String(227), unique=True, nullable=False, primary_key=True)
        sha = db.Column(db.String(128), nullable=False)
        repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
        ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        ownerIdentity = db.Column(db.String(321))
        receiveDate = db.Column(db.DateTime, default=datetime.now)
        authorDate = db.Column(db.DateTime)
        message = db.Column(db.UnicodeText)
        repo = db.relationship("Repo", back_populates="commits")
        owner = db.relationship("User", back_populates="commits")

        def __init__(self, sha, owner, repo, date, message, ownerIdentity):
            self.identifier = f"{repo.route}/{sha}"
            self.sha = sha
            self.repoName = repo.route
            self.repo = repo
            self.ownerName = owner.username
            self.owner = owner
            self.authorDate = datetime.fromtimestamp(int(date))
            self.message = message
            self.ownerIdentity = ownerIdentity


    class Post(db.Model):
        identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True)
        number = db.Column(db.Integer, nullable=False)
        repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
        ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        votes = db.relationship("PostVote", back_populates="post")
        voteSum = db.Column(db.Integer, nullable=False, default=0)

        parentID = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
        state = db.Column(db.SmallInteger, nullable=True, default=1)

        date = db.Column(db.DateTime, default=datetime.now)
        lastUpdated = db.Column(db.DateTime, default=datetime.now)
        subject = db.Column(db.Unicode(384))
        message = db.Column(db.UnicodeText)
        repo = db.relationship("Repo", back_populates="posts")
        owner = db.relationship("User", back_populates="posts")
        parent = db.relationship("Post", back_populates="children", remote_side="Post.identifier")
        children = db.relationship("Post", back_populates="parent", remote_side="Post.parentID")

        def __init__(self, owner, repo, parent, subject, message):
            self.identifier = f"{repo.route}/{repo.lastPostID}"
            self.number = repo.lastPostID
            self.repoName = repo.route
            self.repo = repo
            self.ownerName = owner.username
            self.owner = owner
            self.subject = subject
            self.message = message
            self.parent = parent
            repo.lastPostID += 1

        def updateDate(self):
            self.lastUpdated = datetime.now()
            with db.session.no_autoflush:
                if self.parent is not None:
                    self.parent.updateDate()


    class UserNotification(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        notificationID = db.Column(db.BigInteger, db.ForeignKey("notification.id"))
        attentionLevel = db.Column(db.SmallInteger, nullable=False)  # 0 is read
        readTime = db.Column(db.DateTime, nullable=True)

        user = db.relationship("User", back_populates="notifications")
        notification = db.relationship("Notification", back_populates="notifications")

        __table_args__ = (db.UniqueConstraint("userUsername", "notificationID", name="_user_notification_uc"),)

        def __init__(self, user, notification, level):
            self.userUsername = user.username
            self.notificationID = notification.id
            self.attentionLevel = level

        def read(self):
            self.readTime = datetime.utcnow
            self.attentionLevel = 0


    class UserFollow(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        followerUsername = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False)
        followedUsername = db.Column(db.String(32), db.ForeignKey("user.username", ondelete="CASCADE"), nullable=False)

        follower = db.relationship("User", back_populates="follows", foreign_keys=[followedUsername])
        followed = db.relationship("User", back_populates="followers", foreign_keys=[followerUsername])

        def __init__(self, followerUsername, followedUsername):
            self.followerUsername = followerUsername
            self.followedUsername = followedUsername


    class Notification(db.Model):
        id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
        data = db.Column(db.dialects.postgresql.JSONB, nullable=False, default={})
        notifications = db.relationship("UserNotification", back_populates="notification")
        timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now)

        def __init__(self, json):
            self.data = json

        # def sendTo(self, users, level):
        #     for user in users:
        #         db.session.add(UserNotification(user, self, level))

