from datetime import datetime
from email.policy import default

import flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_httpauth import HTTPBasicAuth
from markupsafe import escape, Markup
from flask_migrate import Migrate
from jinja2_fragments.flask import render_block
from sqlalchemy.orm import backref
import sqlalchemy.dialects.postgresql
from os import path

import config
import markdown


app = flask.Flask(__name__)
bcrypt = Bcrypt(app)


app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
app.config["SECRET_KEY"] = config.DB_PASSWORD


db = SQLAlchemy(app)
migrate = Migrate(app, db)


@app.template_filter("split")
def split(value, separator=None, maxsplit=-1):
    return value.split(separator, maxsplit)



with app.app_context():
    class User(db.Model):
        username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True)
        password_hashed = db.Column(db.String(60), nullable=False)
        admin = db.Column(db.Boolean, nullable=False, default=False, server_default="false")

        def __init__(self, username, password):
            self.username = username
            self.password_hashed = bcrypt.generate_password_hash(password).decode("utf-8")


    class Licence(db.Model):
        id = db.Column(db.String(32), primary_key=True)               # SPDX identifier
        title = db.Column(db.UnicodeText, nullable=False)             # the official name of the licence
        description = db.Column(db.UnicodeText, nullable=False)       # brief description of its permissions and restrictions
        legal_text = db.Column(db.UnicodeText, nullable=False)        # the full legal text of the licence
        url = db.Column(db.String(2048), nullable=True)               # the URL to a page with the full text of the licence and more information
        pictures = db.relationship("PictureLicence", back_populates="licence")

        def __init__(self, id, title, description, legal_text, url):
            self.id = id
            self.title = title
            self.description = description
            self.legal_text = legal_text
            self.url = url


    class PictureLicence(db.Model):
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)

        resource_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"))
        licence_id = db.Column(db.String(32), db.ForeignKey("licence.id"))

        resource = db.relationship("PictureResource", back_populates="licences")
        licence = db.relationship("Licence", back_populates="pictures")

        def __init__(self, resource_id, licence_id):
            self.resource_id = resource_id
            self.licence_id = licence_id


    class Resource(db.Model):
        __abstract__ = True

        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        title = db.Column(db.UnicodeText, nullable=False)
        description = db.Column(db.UnicodeText, nullable=False)
        timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
        origin_url = db.Column(db.String(2048), nullable=True)        # should be left empty if it's original or the source is unknown but public domain


    class PictureNature(db.Model):
        # Examples:
        # "photo", "paper-scan", "2d-art-photo", "sculpture-photo", "computer-3d", "computer-painting",
        # "computer-line-art", "diagram", "infographic", "text", "map", "chart-graph", "screen-capture",
        # "screen-photo", "pattern", "collage", "ai", and so on
        id = db.Column(db.String(64), primary_key=True)
        description = db.Column(db.UnicodeText, nullable=False)
        resources = db.relationship("PictureResource", back_populates="nature")

        def __init__(self, id, description):
            self.id = id
            self.description = description


    class PictureObjectInheritance(db.Model):
        parent_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"),
                              primary_key=True)
        child_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"),
                             primary_key=True)

        parent = db.relationship("PictureObject", foreign_keys=[parent_id],
                                 back_populates="child_links")
        child = db.relationship("PictureObject", foreign_keys=[child_id],
                                back_populates="parent_links")

        def __init__(self, parent, child):
            self.parent = parent
            self.child = child


    class PictureObject(db.Model):
        id = db.Column(db.String(64), primary_key=True)
        description = db.Column(db.UnicodeText, nullable=False)

        child_links = db.relationship("PictureObjectInheritance",
                                      foreign_keys=[PictureObjectInheritance.parent_id],
                                      back_populates="parent")
        parent_links = db.relationship("PictureObjectInheritance",
                                       foreign_keys=[PictureObjectInheritance.child_id],
                                       back_populates="child")

        def __init__(self, id, description):
            self.id = id
            self.description = description


    class PictureRegion(db.Model):
        # This is for picture region annotations
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        json = db.Column(sqlalchemy.dialects.postgresql.JSONB, nullable=False)

        resource_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=False)
        object_id = db.Column(db.String(64), db.ForeignKey("picture_object.id"), nullable=False)

        resource = db.relationship("PictureResource", backref="regions")
        object = db.relationship("PictureObject", backref="regions")

        def __init__(self, json, resource, object):
            self.json = json
            self.resource = resource
            self.object = object


    class PictureResource(Resource):
        # This is only for bitmap pictures. Vectors will be stored under a different model
        # File name is the ID in the picture directory under data, without an extension
        file_format = db.Column(db.String(64), nullable=False)        # MIME type
        width = db.Column(db.Integer, nullable=False)
        height = db.Column(db.Integer, nullable=False)
        nature_id = db.Column(db.String(32), db.ForeignKey("picture_nature.id"), nullable=True)

        nature = db.relationship("PictureNature", back_populates="resources")

        replaces_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"), nullable=True)
        replaced_by_id = db.Column(db.Integer, db.ForeignKey("picture_resource.id"),
                                   nullable=True)

        replaces = db.relationship("PictureResource", remote_side="PictureResource.id",
                                   foreign_keys=[replaces_id], back_populates="replaced_by")
        replaced_by = db.relationship("PictureResource", remote_side="PictureResource.id",
                                      foreign_keys=[replaced_by_id])

        licences = db.relationship("PictureLicence", back_populates="resource")

        def __init__(self, title, description, origin_url, licence_ids, mime, nature=None,
                     replaces=None):
            self.title = title
            self.description = description
            self.origin_url = origin_url
            self.file_format = mime
            self.width = self.height = 0
            self.nature = nature
            db.session.add(self)
            db.session.commit()
            for licence_id in licence_ids:
                joiner = PictureLicence(self.id, licence_id)
                db.session.add(joiner)
            if replaces is not None:
                self.replaces = replaces
                replaces.replaced_by = self


@app.route("/")
def index():
    return flask.render_template("home.html")


@app.route("/accounts")
def accounts():
    return flask.render_template("login.html")


@app.route("/login", methods=["POST"])
def login():
    username = flask.request.form["username"]
    password = flask.request.form["password"]

    user = db.session.get(User, username)

    if user is None:
        flask.flash("This username is not registered.")
        return flask.redirect("/accounts")

    if not bcrypt.check_password_hash(user.password_hashed, password):
        flask.flash("Incorrect password.")
        return flask.redirect("/accounts")

    flask.flash("You have been logged in.")

    flask.session["username"] = username
    return flask.redirect("/")


@app.route("/logout")
def logout():
    flask.session.pop("username", None)
    flask.flash("You have been logged out.")
    return flask.redirect("/")


@app.route("/signup", methods=["POST"])
def signup():
    username = flask.request.form["username"]
    password = flask.request.form["password"]

    if db.session.get(User, username) is not None:
        flask.flash("This username is already taken.")
        return flask.redirect("/accounts")

    if set(username) > set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"):
        flask.flash("Usernames can only contain the Latin alphabet, digits, hyphens, and underscores.")
        return flask.redirect("/accounts")

    if len(username) < 3 or len(username) > 32:
        flask.flash("Usernames must be between 3 and 32 characters long.")
        return flask.redirect("/accounts")

    if len(password) < 6:
        flask.flash("Passwords must be at least 6 characters long.")
        return flask.redirect("/accounts")

    user = User(username, None, password)
    db.session.add(user)
    db.session.commit()

    flask.session["username"] = username

    flask.flash("You have been registered and logged in.")

    return flask.redirect("/")


@app.route("/profile", defaults={"username": None})
@app.route("/profile/<username>")
def profile(username):
    if username is None:
        if "username" in flask.session:
            return flask.redirect("/profile/" + flask.session["username"])
        else:
            flask.flash("Please log in to perform this action.")
            return flask.redirect("/accounts")

    user = db.session.get(User, username)
    if user is None:
        return flask.abort(404)

    return flask.render_template("profile.html", user=user)


@app.route("/upload")
def upload():
    return flask.render_template("upload.html")


@app.route("/upload", methods=["POST"])
def upload_post():
    title = flask.request.form["title"]
    description = flask.request.form["description"]
    origin_url = flask.request.form["origin_url"]

    file = flask.request.files["file"]

    if not file or not file.filename:
        flask.flash("No selected file")
        return flask.redirect(flask.request.url)

    resource = PictureResource(title, description, origin_url, ["CC0-1.0"], file.mimetype)
    db.session.add(resource)
    db.session.commit()
    file.save(path.join(config.DATA_PATH, "pictures", str(resource.id)))

    return flask.redirect("/picture/" + str(resource.id))


@app.route("/picture/<int:id>")
def picture(id):
    resource = db.session.get(PictureResource, id)
    if resource is None:
        return flask.abort(404)

    return flask.render_template("picture.html", resource=resource)


@app.route("/raw/picture/<int:id>")
def raw_picture(id):
    resource = db.session.get(PictureResource, id)
    if resource is None:
        return flask.abort(404)

    response = flask.send_from_directory(path.join(config.DATA_PATH, "pictures"), str(resource.id))
    response.mimetype = resource.file_format

    return response
