from flask import Flask, render_template, redirect, request, session, url_for, g, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import relationship
from flask_admin import Admin, AdminIndexView, expose, BaseView
from flask_admin.contrib.sqla import ModelView
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import StringField, TextAreaField, SelectField
from wtforms.validators import InputRequired
from werkzeug.utils import secure_filename
from functools import wraps
from datetime import datetime
import os
import logging
# import csv
# from sqlalchemy import create_engine, Table, MetaData

app = Flask(__name__)
app.secret_key = 'bebra'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///Picture_Puzzle_web.db'
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif', 'mp4', 'waw', 'pdf'}
db = SQLAlchemy(app)
logging.basicConfig(level=logging.DEBUG)
class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(100), unique=True, nullable=False)
    password = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(100), unique=True, nullable=False)

class Post(db.Model):
    __tablename__ = 'post'
    id = db.Column(db.Integer, primary_key=True)
    date_created = db.Column(db.String(100), unique=False, nullable=False)
    alias = db.Column(db.String(100), unique=True, nullable=False)
    title = db.Column(db.String(100), nullable=False)
    image = db.Column(db.String(100), nullable=False)
    
class ForumCategory(db.Model):
    __tablename__ = 'forumcategory'
    id = db.Column(db.Integer, primary_key=True)
    category_name = db.Column(db.String(100), nullable=False)
    description = db.Column(db.String(200))

class ForumPost(db.Model):
    __tablename__ = 'forumpost'
    id = db.Column(db.Integer, primary_key=True)
    category_id = db.Column(db.Integer, db.ForeignKey('forumcategory.id'), nullable=False)
    post_name = db.Column(db.String(100), nullable=False)
    created_by = db.Column(db.String(100), nullable=False)
    creation_date = db.Column(db.DateTime, default=datetime.now)
    media = db.relationship('Media', backref='forumpost', lazy=True)
    text = db.Column(db.Text, nullable=False)
    edited = db.Column(db.Boolean, default=False)
    upvotes = db.Column(db.Integer, default=0)
    downvotes = db.Column(db.Integer, default=0)

class ForumComment(db.Model):
    __tablename__ = 'forumcomment'
    id = db.Column(db.Integer, primary_key=True)
    post_id = db.Column(db.Integer, db.ForeignKey('forumpost.id'), nullable=False)
    created_by = db.Column(db.String(100), nullable=False)
    creation_date = db.Column(db.DateTime, nullable=False)
    media = db.Column(db.Integer)
    text = db.Column(db.Text, nullable=False)
    edited = db.Column(db.Boolean, default=False)
    upvotes = db.Column(db.Integer, default=0)
    downvotes = db.Column(db.Integer, default=0)

    post = relationship("ForumPost", primaryjoin="foreign(ForumComment.post_id) == remote(ForumPost.id)")
    
class Media(db.Model):
    __tablename__ = 'media'
    id = db.Column(db.Integer, primary_key=True)
    post_id = db.Column(db.Integer, db.ForeignKey('forumpost.id'), nullable=False)
    filename = db.Column(db.String(100), nullable=False)
    
class CreatePostForm(FlaskForm):
    category_id = SelectField('Category', validators=[InputRequired()], coerce=int)
    post_name = StringField('Post Title', validators=[InputRequired()])
    created_by = StringField('Your Name', validators=[InputRequired()])
    text = TextAreaField('Post Content', validators=[InputRequired()])
    media = FileField('Insert Media', validators=[FileAllowed(app.config['ALLOWED_EXTENSIONS'])])


def admin_login_required(view_func):
    @wraps(view_func)
    def decorated_function(*args, **kwargs):
        if not session.get('admin_logged_in'):
            return redirect(url_for('admin_login'))
        return view_func(*args, **kwargs)
    return decorated_function

class MyAdminIndexView(AdminIndexView):
    @expose('/')
    @admin_login_required
    def index(self):
        return self.render('admin/index.html')

class UserAdminView(ModelView):
    column_exclude_list = ['password']
    can_export = True
    export_types = ['csv']

class AdminView(ModelView):
    can_export = True
    export_types = ['csv']

class LogoutView(BaseView):
    @expose('/')
    def index(self):
        session.pop("admin_logged_in", None)
        return redirect(url_for("index"))

admin = Admin(app, name='Admin Panel', template_mode='bootstrap3', index_view=MyAdminIndexView())
admin.add_view(UserAdminView(User, db.session))
admin.add_view(AdminView(Post, db.session))
admin.add_view(AdminView(ForumCategory, db.session))
admin.add_view(AdminView(ForumPost, db.session))
admin.add_view(AdminView(ForumComment, db.session))
admin.add_view(AdminView(Media, db.session))
admin.add_view(LogoutView(name='Logout', endpoint='admin_logout'))

@app.before_request
def check_admin_login():
    if request.path.startswith('/admin/') and not session.get('admin_logged_in'):
        if request.path != '/admin/login' and request.path != '/admin/logout':
            return redirect(url_for('admin_login'))

ADMIN_USERNAME = 'user'
ADMIN_PASSWORD = '1234321'

@app.before_request
def before_request():
    g.user = None
    if 'username' in session:
        user = User.query.filter_by(username=session['username']).first()
        g.user = user

@app.route("/admin/login", methods=["GET", "POST"])
def admin_login():
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
            session["admin_logged_in"] = True
            return redirect(url_for("admin.index"))
        else:
            return render_template("admin/login.html", error_msg="Invalid credentials")
    return render_template("admin/login.html", error_msg=None)

@app.route("/admin/logout")
def admin_logout():
    session.pop("admin_logged_in", None)
    return redirect(url_for("admin_login"))

@app.route("/register", methods=["GET", "POST"])
def register():
    if request.method == "POST":
        username = request.form["username"]
        email = request.form["email"]
        password = request.form["password"]

        existing_user = User.query.filter_by(email=email).first()
        if existing_user:
            error_msg = "Email already exists"
            return render_template("auth/register.html", error_msg=error_msg)

        existing_username = User.query.filter_by(username=username).first()
        if existing_username:
            error_msg = "Username already exists"
            return render_template("auth/register.html", error_msg=error_msg)

        new_user = User(username=username, email=email, password=password)  # type: ignore
        db.session.add(new_user)
        db.session.commit()
        return redirect(url_for("login"))

    return render_template("auth/register.html")

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        user = User.query.filter_by(username=username, password=password).first()
        if user:
            session["username"] = username
            return redirect(url_for("index"))
    return render_template("auth/login.html")

@app.route("/logout")
def logout():
    session.pop("username", None)
    return redirect(url_for("index"))

@app.route("/")
def index():
    latest_posts = Post.query.all()[::-1]
    return render_template("index.html", latest_posts=latest_posts)

@app.route("/posts")
def all_posts():
    all_posts = Post.query.all()[::-1]
    return render_template("posts.html", posts=all_posts)

@app.route("/posts/<alias>")
def post(alias):
    post_info = Post.query.filter_by(alias=alias).first()
    if post_info:
        return render_template(f"{alias}.html", post_info=post_info)
    else:
        return "Post not found", 404

@app.route('/forums')
def forums():
    categories = ForumCategory.query.all()
    return render_template('forums.html', categories=categories)

@app.route('/forums/<category_name>')
def category(category_name):
    category_name = category_name.capitalize()
    print("Received category name:", category_name)
    category = ForumCategory.query.filter_by(category_name=category_name).first()
    if category:
        posts = ForumPost.query.filter_by(category_id=category.id).limit(10).all()
        return render_template('category.html', category=category, posts=posts)
    else:
        return "Category not found", 404

@app.route('/forums/<int:category_id>/create_post', methods=['GET', 'POST'])
def create_post(category_id):
    form = CreatePostForm()
    category = ForumCategory.query.get_or_404(category_id)

    form.category_id.choices = [(category.id, category.category_name) for category in ForumCategory.query.all()]

    if not g.user:
        return redirect(url_for('login'))

    if request.method == 'POST':
        post_name = request.form['post_name']
        text = request.form['text']
        media_files = request.files.getlist('media')
        
        media_filenames = []
        for file in media_files:
            if file:
                filename = secure_filename(file.filename)
                file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
                media_filenames.append(filename)

        user_nickname = g.user.username
        
        new_post = ForumPost(
            category_id=category_id,
            post_name=post_name,
            created_by=user_nickname,
            text=text,
            creation_date=datetime.now(),
            edited=False
        )
        db.session.add(new_post)
        db.session.commit()
        
        for filename in media_filenames:
            new_media = Media(post_id=new_post.id, filename=filename)
            db.session.add(new_media)
        
        db.session.commit()
        
        return redirect(url_for('category', category_name=category.category_name.lower()))

    return render_template('create_post.html', form=form, category=category)

@app.route('/forums/<category_name>/<int:post_id>', methods=['GET', 'POST'])
def view_post(category_name, post_id):
    post = ForumPost.query.get_or_404(post_id)
    comments = ForumComment.query.filter_by(post_id=post_id).all()
    if request.method == 'POST':
        created_by = request.form['created_by']
        text = request.form['text']
        new_comment = ForumComment(post_id=post_id, created_by=created_by, text=text)  # type: ignore
        db.session.add(new_comment)
        db.session.commit()
        return redirect(url_for('view_post', category_name=category_name, post_id=post_id))
    return render_template('post.html', post=post, comments=comments)

@app.route('/forums/upvote_post/<int:post_id>')
def upvote_post(post_id):
    post = ForumPost.query.get_or_404(post_id)
    post.upvotes += 1
    db.session.commit()
    return redirect(request.referrer)

@app.route('/forums/downvote_post/<int:post_id>')
def downvote_post(post_id):
    post = ForumPost.query.get_or_404(post_id)
    post.downvotes += 1
    db.session.commit()
    return redirect(request.referrer)

@app.route('/forums/upvote_comment/<int:comment_id>')
def upvote_comment(comment_id):
    comment = ForumComment.query.get_or_404(comment_id)
    comment.upvotes += 1
    db.session.commit()
    return redirect(request.referrer)

@app.route('/forums/downvote_comment/<int:comment_id>')
def downvote_comment(comment_id):
    comment = ForumComment.query.get_or_404(comment_id)
    comment.downvotes += 1
    db.session.commit()
    return redirect(request.referrer)

@app.route('/uploads/<path:filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

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


def recreate_database():
    if os.path.exists('Picture_Puzzle_web.db'):
        os.remove('Picture_Puzzle_web.db')
    with app.app_context():
        db.create_all()


if __name__ == '__main__':
    recreate_database()
    app.run(debug=True)