# ContentDB
# Copyright (C) 2022 rubenwardy
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

from flask import Blueprint, request, render_template, url_for, abort, flash
from flask_babel import lazy_gettext
from flask_login import current_user
from flask_wtf import FlaskForm
from werkzeug.utils import redirect
from wtforms import TextAreaField, SubmitField, URLField, StringField, SelectField, FileField
from wtforms.validators import InputRequired, Length, Optional, DataRequired

from app.logic.uploads import upload_file
from app.models import User, UserRank, Report, db, AuditSeverity, ReportCategory, Thread, Permission, ReportAttachment
from app.tasks.webhooktasks import post_discord_webhook
from app.utils import (is_no, abs_url_samesite, normalize_line_endings, rank_required, add_audit_log, abs_url_for,
					   random_string, add_replies)

bp = Blueprint("report", __name__)


class ReportForm(FlaskForm):
	category = SelectField(lazy_gettext("Category"), [DataRequired()], choices=ReportCategory.choices(with_none=True), coerce=ReportCategory.coerce)

	url = URLField(lazy_gettext("URL"), [Optional()])
	title = StringField(lazy_gettext("Subject / Title"), [InputRequired(), Length(10, 300)])
	message = TextAreaField(lazy_gettext("Message"), [Optional(), Length(0, 10000)], filters=[normalize_line_endings])
	file_upload = FileField(lazy_gettext("Image Upload"), [Optional()])
	submit = SubmitField(lazy_gettext("Report"))


@bp.route("/report/", methods=["GET", "POST"])
def report():
	is_anon = not current_user.is_authenticated or not is_no(request.args.get("anon"))

	url = request.args.get("url")
	if url:
		if url.startswith("/report/"):
			abort(404)

		url = abs_url_samesite(url)

	form = ReportForm() if current_user.is_authenticated else None
	if form and request.method == "GET":
		try:
			form.category.data = ReportCategory.coerce(request.args.get("category"))
		except KeyError:
			pass
		form.url.data = url
		form.title.data = request.args.get("title", "")

	if form and form.validate_on_submit():
		report = Report()
		report.id = random_string(8)
		report.user = current_user if current_user.is_authenticated else None
		form.populate_obj(report)

		if current_user.is_authenticated:
			thread = Thread()
			thread.title = f"Report: {form.title.data}"
			thread.author = current_user
			thread.private = True
			thread.watchers.extend(User.query.filter(User.rank >= UserRank.MODERATOR).all())
			db.session.add(thread)
			db.session.flush()

			report.thread = thread

			add_replies(thread, current_user, f"**{report.category.title} report created**\n\n{form.message.data}")
		else:
			ip_addr = request.headers.get("X-Forwarded-For") or request.remote_addr
			report.message = ip_addr + "\n\n" + report.message

		db.session.add(report)
		db.session.flush()

		if form.file_upload.data:
			atmt = ReportAttachment()
			report.attachments.add(atmt)
			uploaded_url, _ = upload_file(form.file_upload.data, "image", lazy_gettext("a PNG, JPEG, or WebP image file"))
			atmt.url = uploaded_url
			db.session.add(atmt)

		if current_user.is_authenticated:
			add_audit_log(AuditSeverity.USER, current_user, f"New report: {report.title}",
					url_for("report.view", rid=report.id))

		db.session.commit()

		abs_url = abs_url_for("report.view", rid=report.id)
		msg = f"**New Report**\nReport on `{report.url}`\n\n{report.title}\n\nView: {abs_url}"
		post_discord_webhook.delay(None if is_anon else current_user.username, msg, True)

		return redirect(url_for("report.report_received", rid=report.id))

	return render_template("report/report.html", form=form, url=url, is_anon=is_anon, noindex=url is not None)


@bp.route("/report/received/")
def report_received():
	rid = request.args.get("rid")
	report = Report.query.get_or_404(rid)
	return render_template("report/report_received.html", report=report)


@bp.route("/admin/reports/")
@rank_required(UserRank.EDITOR)
def list_all():
	reports = Report.query.order_by(db.asc(Report.is_resolved), db.asc(Report.created_at)).all()
	return render_template("report/list.html", reports=reports)


@bp.route("/admin/reports/<rid>/", methods=["GET", "POST"])
def view(rid: str):
	report = Report.query.get_or_404(rid)
	if not report.check_perm(current_user, Permission.SEE_REPORT):
		abort(404)

	if request.method == "POST":
		if report.is_resolved:
			if "reopen" in request.form:
				report.is_resolved = False
				url = url_for("report.view", rid=report.id)
				add_audit_log(AuditSeverity.MODERATION, current_user, f"Reopened report \"{report.title}\"", url)

				if report.thread:
					add_replies(report.thread, current_user, f"Reopened report", is_status_update=True)

				db.session.commit()
		else:
			if "completed" in request.form:
				outcome = "as completed"
			elif "removed" in request.form:
				outcome = "as content removed"
			elif "invalid" in request.form:
				outcome = "without action"
				if report.thread:
					flash("Make sure to comment why the report is invalid in the thread", "warning")
			else:
				abort(400)

			report.is_resolved = True
			url = url_for("report.view", rid=report.id)
			add_audit_log(AuditSeverity.MODERATION, current_user, f"Report closed {outcome} \"{report.title}\"", url)

			if report.thread:
				add_replies(report.thread, current_user, f"Closed report {outcome}", is_status_update=True)

			db.session.commit()

	return render_template("report/view.html", report=report)


@bp.route("/admin/reports/<rid>/edit/", methods=["GET", "POST"])
def edit(rid: str):
	report = Report.query.get_or_404(rid)
	if not report.check_perm(current_user, Permission.SEE_REPORT):
		abort(404)

	form = ReportForm(request.form, obj=report)
	form.submit.label.text = lazy_gettext("Save")
	if form.validate_on_submit():
		form.populate_obj(report)
		url = url_for("report.view", rid=report.id)
		add_audit_log(AuditSeverity.MODERATION, current_user, f"Edited report \"{report.title}\"", url)
		db.session.commit()

		return redirect(url_for("report.view", rid=report.id))

	return render_template("report/edit.html", report=report, form=form)
