from app import app, db, bcrypt
import git
from datetime import datetime
from enum import Enum
from PIL import Image
from cairosvg import svg2png
import os
import config
import cairosvg
import random

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

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

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

        __table_args__ = (db.UniqueConstraint("user_username", "repo_route", name="_user_repo_uc"),)

        def __init__(self, user, repo, level):
            self.user_username = user.username
            self.repo_route = repo.route
            self.access_level = level


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

        notify_commit = db.Column(db.Boolean)

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

        __table_args__ = (db.UniqueConstraint("user_username", "repo_route", name="_user_repo_uc1"),)

        def __init__(self, user, repo):
            self.user_username = user.username
            self.repo_route = repo.route


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

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

        __table_args__ = (db.UniqueConstraint("user_username", "post_identifier", name="_user_post_uc"),)

        def __init__(self, user, post, score):
            self.user_username = user.username
            self.post_identifier = post.identifier
            self.vote_score = score


    class User(db.Model):
        username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True)
        display_name = db.Column(db.Unicode(128), unique=False, nullable=True)
        bio = db.Column(db.Unicode(16384), unique=False, nullable=True)
        password_hashed = db.Column(db.String(60), nullable=False)
        email = db.Column(db.String(254), nullable=True)
        company = db.Column(db.Unicode(64), nullable=True)
        company_url = db.Column(db.String(256), nullable=True)
        url = db.Column(db.String(256), nullable=True)
        show_mail = db.Column(db.Boolean, default=False, nullable=False)
        location = db.Column(db.Unicode(64), nullable=True)
        creation_date = db.Column(db.DateTime, default=datetime.utcnow)

        repositories = db.relationship("Repo", back_populates="owner")
        followers = db.relationship("UserFollow", back_populates="followed", foreign_keys="[UserFollow.followed_username]")
        follows = db.relationship("UserFollow", back_populates="follower", foreign_keys="[UserFollow.follower_username]")
        repo_access = 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")
        prs = db.relationship("PullRequest", back_populates="owner")
        notifications = db.relationship("UserNotification", back_populates="user")

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

            # 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))

            avatar_name = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH))
            if os.path.join(config.DEFAULT_AVATARS_PATH, avatar_name).endswith(".svg"):
                cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatar_name),
                                 write_to="/tmp/roundabout-avatar.png")
                avatar = Image.open("/tmp/roundabout-avatar.png")
            else:
                avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatar_name))
            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)
        owner_name = 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)
        creation_date = db.Column(db.DateTime, default=datetime.utcnow)

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

        commits = db.relationship("Commit", back_populates="repo")
        posts = db.relationship("Post", back_populates="repo")
        repo_access = db.relationship("RepoAccess", back_populates="repo")
        favourites = db.relationship("RepoFavourite", back_populates="repo")
        heads = db.relationship("PullRequest", back_populates="head", foreign_keys="[PullRequest.head_route]")
        bases = db.relationship("PullRequest", back_populates="base", foreign_keys="[PullRequest.base_route]")

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

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

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


    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)
        repo_name = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
        owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        owner_identity = db.Column(db.String(321))
        receive_date = db.Column(db.DateTime, default=datetime.now)
        author_date = 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, owner_identity):
            self.identifier = f"{repo.route}/{sha}"
            self.sha = sha
            self.repo_name = repo.route
            self.repo = repo
            self.owner_name = owner.username
            self.owner = owner
            self.author_date = datetime.fromtimestamp(int(date))
            self.message = message
            self.owner_identity = owner_identity


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

        parent_id = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
        root_id = 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)
        last_updated = 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",
                                 primaryjoin="Post.parent_id==Post.identifier",
                                 foreign_keys="[Post.parent_id]", remote_side="Post.identifier")
        root = db.relationship("Post",
                               primaryjoin="Post.root_id==Post.identifier",
                               foreign_keys="[Post.root_id]", remote_side="Post.identifier")
        children = db.relationship("Post",
                                   remote_side="Post.parent_id",
                                   primaryjoin="Post.identifier==Post.parent_id",
                                   foreign_keys="[Post.parent_id]")

        def __init__(self, owner, repo, parent, subject, message):
            self.identifier = f"{repo.route}/{repo.last_post_id}"
            self.number = repo.last_post_id
            self.repo_name = repo.route
            self.repo = repo
            self.owner_name = owner.username
            self.owner = owner
            self.subject = subject
            self.message = message
            self.parent = parent
            if parent and parent.parent:
                self.root = parent.parent
            elif parent:
                self.root = parent
            else:
                self.root = None
            repo.last_post_id += 1

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


    class UserNotification(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        user_username = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        notification_id = db.Column(db.BigInteger, db.ForeignKey("notification.id"))
        attention_level = db.Column(db.SmallInteger, nullable=False)  # 0 is read
        read_time = db.Column(db.DateTime, nullable=True)

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

        __table_args__ = (db.UniqueConstraint("user_username", "notification_id", name="_user_notification_uc"),)

        def __init__(self, user, notification, level):
            self.user_username = user.username
            self.notification_id = notification.id
            self.attention_level = level

        def read(self):
            self.read_time = datetime.utcnow
            self.attention_level = 0


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

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

        def __init__(self, follower_username, followed_username):
            self.follower_username = follower_username
            self.followed_username = followed_username


    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


    class PullRequest(db.Model):
        id = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
        head_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False)
        base_route = db.Column(db.String(98), db.ForeignKey("repo.route", ondelete="CASCADE"), nullable=False)
        owner_name = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
        state = db.Column(db.SmallInteger, nullable=False, default=0)                # 0 pending, 1 merged, 2 rejected

        head = db.relationship("Repo", back_populates="heads", foreign_keys=[head_route])
        base = db.relationship("Repo", back_populates="bases", foreign_keys=[base_route])

        head_branch = db.Column(db.String(64), nullable=False)
        base_branch = db.Column(db.String(64), nullable=False)

        owner = db.relationship("User", back_populates="prs")
        timestamp = db.Column(db.DateTime, nullable=False, default=datetime.now)

        def __init__(self, head, head_branch, base, base_branch, owner):
            self.head = head
            self.base = base
            self.head_branch = head_branch
            self.base_branch = base_branch
            self.owner = owner
