import logging
from typing import Optional, List, Any

import sqlalchemy.exc
from flask import flash, redirect, url_for, render_template, Response, Blueprint, current_app
from flask.typing import ResponseReturnValue
from flask_wtf import FlaskForm
from sqlalchemy import exc
from wtforms import StringField, SelectField, SubmitField, BooleanField, FileField
from wtforms.validators import DataRequired

from app.brm.static import create_static_origin
from app.models.base import Group
from app.models.cloud import CloudAccount, CloudProvider
from app.models.mirrors import StaticOrigin, Origin
from app.portal.util import response_404, view_lifecycle

bp = Blueprint("static", __name__)


class StaticOriginForm(FlaskForm):  # type: ignore
    description = StringField(
        'Description',
        validators=[DataRequired()],
        description='Enter a brief description of the static website that you are creating in this field. This is '
                    'also a required field.'
    )
    group = SelectField(
        'Group',
        validators=[DataRequired()],
        description='Select the group that you want the origin to belong to from the drop-down menu in this field. '
                    'This is a required field.'
    )
    storage_cloud_account = SelectField(
        'Storage Cloud Account',
        validators=[DataRequired()],
        description='Select the cloud account that you want the origin to be deployed to from the drop-down menu in '
                    'this field. This is a required field.'
    )
    source_cloud_account = SelectField(
        'Source Cloud Account',
        validators=[DataRequired()],
        description='Select the cloud account that will be used to modify the source repository for the web content '
                    'for this static origin. This is a required field.'
    )
    source_project = StringField(
        'Source Project',
        validators=[DataRequired()],
        description='GitLab project path.'
    )
    auto_rotate = BooleanField(
        'Auto-Rotate',
        default=True,
        description='Select this field if you want to enable auto-rotation for the mirror. This means that the mirror '
                    'will automatically redeploy with a new domain name if it is detected to be blocked. This field '
                    'is optional and is enabled by default.'
    )
    matrix_homeserver = SelectField(
        'Matrix Homeserver',
        description='Select the Matrix homeserver from the drop-down box to enable Keanu Convene on mirrors of this '
                    'static origin.'
    )
    keanu_convene_path = StringField(
        'Keanu Convene Path',
        default='talk',
        description='Enter the subdirectory to present the Keanu Convene application at on the mirror. This defaults '
                    'to "talk".'
    )
    keanu_convene_logo = FileField(
        'Keanu Convene Logo',
        description='Logo to use for Keanu Convene'
    )
    keanu_convene_color = StringField(
        'Keanu Convene Accent Color',
        default='#0047ab',
        description='Accent color to use for Keanu Convene (HTML hex code)'
    )
    enable_clean_insights = BooleanField(
        'Enable Clean Insights',
        description='When enabled, a Clean Insights Measurement Proxy endpoint is deployed on the mirror to allow for '
                    'submission of results from any of the supported Clean Insights SDKs.'
    )
    submit = SubmitField('Save Changes')

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self.group.choices = [(x.id, x.group_name) for x in Group.query.all()]
        self.storage_cloud_account.choices = [(x.id, f"{x.provider.description} - {x.description}") for x in
                                              CloudAccount.query.filter(
                                                  CloudAccount.provider == CloudProvider.AWS).all()]
        self.source_cloud_account.choices = [(x.id, f"{x.provider.description} - {x.description}") for x in
                                             CloudAccount.query.filter(
                                                 CloudAccount.provider == CloudProvider.GITLAB).all()]
        self.matrix_homeserver.choices = [(x, x) for x in current_app.config['MATRIX_HOMESERVERS']]


@bp.route("/new", methods=['GET', 'POST'])
@bp.route("/new/<group_id>", methods=['GET', 'POST'])
def static_new(group_id: Optional[int] = None) -> ResponseReturnValue:
    form = StaticOriginForm()
    if len(form.source_cloud_account.choices) == 0 or len(form.storage_cloud_account.choices) == 0:
        flash("You must add at least one AWS account and at least one GitLab account before creating static origins.",
              "warning")
        return redirect(url_for("portal.cloud.cloud_account_list"))
    if form.validate_on_submit():
        try:
            static = create_static_origin(
                form.description.data,
                int(form.group.data),
                int(form.storage_cloud_account.data),
                int(form.source_cloud_account.data),
                form.source_project.data,
                form.auto_rotate.data,
                form.matrix_homeserver.data,
                form.keanu_convene_path.data,
                form.keanu_convene_logo.data,
                form.keanu_convene_color.data,
                form.enable_clean_insights.data,
                True
            )
            flash(f"Created new static origin #{static.id}.", "success")
            return redirect(url_for("portal.static.static_edit", static_id=static.id))
        except ValueError as e:  # may be returned by create_static_origin and from the int conversion
            logging.warning(e)
            flash("Failed to create new static origin due to an invalid input.", "danger")
            return redirect(url_for("portal.static.static_list"))
        except exc.SQLAlchemyError as e:
            flash("Failed to create new static origin due to a database error.", "danger")
            logging.warning(e)
            return redirect(url_for("portal.static.static_list"))
    if group_id:
        form.group.data = group_id
    return render_template("new.html.j2", section="static", form=form)


@bp.route('/edit/<static_id>', methods=['GET', 'POST'])
def static_edit(static_id: int) -> ResponseReturnValue:
    static_origin: Optional[StaticOrigin] = StaticOrigin.query.filter(StaticOrigin.id == static_id).first()
    if static_origin is None:
        return Response(render_template("error.html.j2",
                                        section="static",
                                        header="404 Origin Not Found",
                                        message="The requested static origin could not be found."),
                        status=404)
    form = StaticOriginForm(description=static_origin.description,
                            group=static_origin.group_id,
                            storage_cloud_account=static_origin.storage_cloud_account_id,
                            source_cloud_account=static_origin.source_cloud_account_id,
                            source_project=static_origin.source_project,
                            matrix_homeserver=static_origin.matrix_homeserver,
                            keanu_convene_path=static_origin.keanu_convene_path,
                            auto_rotate=static_origin.auto_rotate,
                            enable_clean_insights=bool(static_origin.clean_insights_backend))
    form.group.render_kw = {"disabled": ""}
    form.storage_cloud_account.render_kw = {"disabled": ""}
    form.source_cloud_account.render_kw = {"disabled": ""}
    if form.validate_on_submit():
        try:
            static_origin.update(
                form.source_project.data,
                form.description.data,
                form.auto_rotate.data,
                form.matrix_homeserver.data,
                form.keanu_convene_path.data,
                form.keanu_convene_logo.data,
                form.keanu_convene_color.data,
                form.enable_clean_insights.data,
                True
            )
            flash("Saved changes to group.", "success")
        except ValueError as e:  # may be returned by create_static_origin and from the int conversion
            logging.warning(e)
            flash("An error occurred saving the changes to the static origin due to an invalid input.", "danger")
        except exc.SQLAlchemyError as e:
            logging.warning(e)
            flash("An error occurred saving the changes to the static origin due to a database error.", "danger")
    try:
        origin = Origin.query.filter_by(domain_name=static_origin.origin_domain_name).one()
        proxies = origin.proxies
    except sqlalchemy.exc.NoResultFound:
        proxies = []
    return render_template("static.html.j2",
                           section="static",
                           static=static_origin, form=form,
                           proxies=proxies)


@bp.route("/list")
def static_list() -> ResponseReturnValue:
    statics: List[StaticOrigin] = StaticOrigin.query.order_by(StaticOrigin.description).all()
    return render_template("list.html.j2",
                           section="static",
                           title="Static Origins",
                           item="static",
                           new_link=url_for("portal.static.static_new"),
                           items=statics
                           )


@bp.route("/destroy/<static_id>", methods=['GET', 'POST'])
def static_destroy(static_id: int) -> ResponseReturnValue:
    static = StaticOrigin.query.filter(StaticOrigin.id == static_id, StaticOrigin.destroyed.is_(None)).first()
    if static is None:
        return response_404("The requested static origin could not be found.")
    return view_lifecycle(
        header=f"Destroy static origin {static.description}",
        message=static.description,
        success_message="All proxies from the destroyed static origin will shortly be destroyed at their providers, "
                        "and the static content will be removed from the cloud provider.",
        success_view="portal.static.static_list",
        section="static",
        resource=static,
        action="destroy"
    )
