from typing import List, Union, Optional, Dict, Type

from flask import render_template, url_for, redirect, Blueprint
from flask.typing import ResponseReturnValue
from flask_wtf import FlaskForm
from wtforms import SelectField, StringField, SubmitField, IntegerField, BooleanField, Form, FormField
from wtforms.validators import InputRequired

from app.extensions import db
from app.models.cloud import CloudAccount, CloudProvider

bp = Blueprint("cloud", __name__)

_SECTION_TEMPLATE_VARS = {
    "section": "cloud",
    "help_url": "https://bypass.censorship.guide/user/cloud.html"
}


class NewCloudAccountForm(FlaskForm):  # type: ignore
    provider = SelectField('Cloud Provider', validators=[InputRequired()])
    submit = SubmitField('Next')


class AWSAccountForm(FlaskForm):  # type: ignore
    provider = StringField('Platform', render_kw={"disabled": ""})
    description = StringField('Description', validators=[InputRequired()])
    aws_access_key = StringField('AWS Access Key', validators=[InputRequired()])
    aws_secret_key = StringField('AWS Secret Key', validators=[InputRequired()])
    aws_region = StringField('AWS Region', default='us-east-2', validators=[InputRequired()])
    max_distributions = IntegerField('Cloudfront Distributions Quota', default=200,
                                     description="This is the quota for number of distributions per account.",
                                     validators=[InputRequired()])
    max_instances = IntegerField('EC2 Instance Quota', default=2,
                                 description="This can be impacted by a number of quotas including instance limits "
                                             "and IP address limits.",
                                 validators=[InputRequired()])
    enabled = BooleanField('Enable this account', default=True,
                           description="New resources will not be deployed to disabled accounts, however existing "
                                       "resources will persist until destroyed at the end of their lifecycle.")
    submit = SubmitField('Save Changes')


class HcloudAccountForm(FlaskForm):  # type: ignore
    provider = StringField('Platform', render_kw={"disabled": ""})
    description = StringField('Description', validators=[InputRequired()])
    hcloud_token = StringField('Hetzner Cloud Token', validators=[InputRequired()])
    max_instances = IntegerField('Server Limit', default=10,
                                 validators=[InputRequired()])
    enabled = BooleanField('Enable this account', default=True,
                           description="New resources will not be deployed to disabled accounts, however existing "
                                       "resources will persist until destroyed at the end of their lifecycle.")
    submit = SubmitField('Save Changes')


class GitlabAccountForm(FlaskForm):  # type: ignore
    provider = StringField('Platform', render_kw={"disabled": ""})
    description = StringField('Description', validators=[InputRequired()])
    gitlab_token = StringField('GitLab Access Token', validators=[InputRequired()])
    enabled = BooleanField('Enable this account', default=True,
                           description="New resources will not be deployed to disabled accounts, however existing "
                                       "resources will persist until destroyed at the end of their lifecycle.")
    submit = SubmitField('Save Changes')


class OvhHorizonForm(Form):  # type: ignore[misc]
    ovh_openstack_user = StringField("User")
    ovh_openstack_password = StringField("Password")
    ovh_openstack_tenant_id = StringField("Tenant ID")


class OvhApiForm(Form):  # type: ignore[misc]
    ovh_cloud_application_key = StringField("Application Key")
    ovh_cloud_application_secret = StringField("Application Secret")
    ovh_cloud_consumer_key = StringField("Consumer Key")


class OvhAccountForm(FlaskForm):  # type: ignore
    provider = StringField('Platform', render_kw={"disabled": ""})
    description = StringField('Description', validators=[InputRequired()])
    horizon = FormField(OvhHorizonForm, 'OpenStack Horizon API')
    ovh_api = FormField(OvhApiForm, 'OVH API')
    max_instances = IntegerField('Server Limit', default=10,
                                 validators=[InputRequired()])
    enabled = BooleanField('Enable this account', default=True,
                           description="New resources will not be deployed to disabled accounts, however existing "
                                       "resources will persist until destroyed at the end of their lifecycle.")
    submit = SubmitField('Save Changes')


class GandiHorizonForm(Form):  # type: ignore[misc]
    gandi_openstack_user = StringField("User")
    gandi_openstack_password = StringField("Password")
    gandi_openstack_tenant_id = StringField("Tenant ID")


class GandiAccountForm(FlaskForm):  # type: ignore
    provider = StringField('Platform', render_kw={"disabled": ""})
    description = StringField('Description', validators=[InputRequired()])
    horizon = FormField(GandiHorizonForm, 'OpenStack Horizon API')
    max_instances = IntegerField('Server Limit', default=10,
                                 validators=[InputRequired()])
    enabled = BooleanField('Enable this account', default=True,
                           description="New resources will not be deployed to disabled accounts, however existing "
                                       "resources will persist until destroyed at the end of their lifecycle.")
    submit = SubmitField('Save Changes')


CloudAccountForm = Union[AWSAccountForm, HcloudAccountForm, GandiAccountForm, OvhAccountForm]

provider_forms: Dict[str, Type[CloudAccountForm]] = {
    CloudProvider.AWS.name: AWSAccountForm,
    CloudProvider.HCLOUD.name: HcloudAccountForm,
    CloudProvider.GANDI.name: GandiAccountForm,
    CloudProvider.GITLAB.name: GitlabAccountForm,
    CloudProvider.OVH.name: OvhAccountForm,
}


def cloud_account_save(account: Optional[CloudAccount], provider: CloudProvider, form: CloudAccountForm) -> None:
    if not account:
        account = CloudAccount()
        account.provider = provider
        db.session.add(account)
    if account.provider != provider:
        raise RuntimeError("Provider mismatch in saving cloud account.")
    account.description = form.description.data
    account.enabled = form.enabled.data
    if provider == CloudProvider.AWS and isinstance(form, AWSAccountForm):
        account.credentials = {
            "aws_access_key": form.aws_access_key.data,
            "aws_secret_key": form.aws_secret_key.data,
            "aws_region": form.aws_region.data,
        }
        account.max_distributions = form.max_distributions.data
        account.max_sub_distributions = 1
        account.max_instances = form.max_instances.data
    elif provider == CloudProvider.HCLOUD and isinstance(form, HcloudAccountForm):
        account.credentials = {
            "hcloud_token": form.hcloud_token.data,
        }
        account.max_distributions = 0
        account.max_sub_distributions = 0
        account.max_instances = form.max_instances.data
    elif provider == CloudProvider.GANDI and isinstance(form, GandiAccountForm):
        account.credentials = {
            "gandi_openstack_user": form.horizon.data["gandi_openstack_user"],
            "gandi_openstack_password": form.horizon.data["gandi_openstack_password"],
            "gandi_openstack_tenant_id": form.horizon.data["gandi_openstack_tenant_id"],
        }
        account.max_distributions = 0
        account.max_sub_distributions = 0
        account.max_instances = form.max_instances.data
    elif provider == CloudProvider.GITLAB and isinstance(form, GitlabAccountForm):
        account.credentials = {
            "gitlab_token": form.gitlab_token.data,
        }
    elif provider == CloudProvider.OVH and isinstance(form, OvhAccountForm):
        account.credentials = {
            "ovh_openstack_user": form.horizon.data["ovh_openstack_user"],
            "ovh_openstack_password": form.horizon.data["ovh_openstack_password"],
            "ovh_openstack_tenant_id": form.horizon.data["ovh_openstack_tenant_id"],
            "ovh_cloud_application_key": form.ovh_api.data["ovh_cloud_application_key"],
            "ovh_cloud_application_secret": form.ovh_api.data["ovh_cloud_application_secret"],
            "ovh_cloud_consumer_key": form.ovh_api.data["ovh_cloud_consumer_key"],
        }
        account.max_distributions = 0
        account.max_sub_distributions = 0
        account.max_instances = form.max_instances.data
    else:
        raise RuntimeError("Unknown provider or form data did not match provider.")


def cloud_account_populate(form: CloudAccountForm, account: CloudAccount) -> None:
    form.provider.data = account.provider.description
    form.description.data = account.description
    form.enabled.data = account.enabled
    if account.provider == CloudProvider.AWS and isinstance(form, AWSAccountForm):
        form.aws_access_key.data = account.credentials["aws_access_key"]
        form.aws_secret_key.data = account.credentials["aws_secret_key"]
        form.aws_region.data = account.credentials["aws_region"]
        form.max_distributions.data = account.max_distributions
        form.max_instances.data = account.max_instances
    elif account.provider == CloudProvider.HCLOUD and isinstance(form, HcloudAccountForm):
        form.hcloud_token.data = account.credentials["hcloud_token"]
        form.max_instances.data = account.max_instances
    elif account.provider == CloudProvider.GANDI and isinstance(form, GandiAccountForm):
        form.horizon.form.gandi_openstack_user.data = account.credentials["gandi_openstack_user"]
        form.horizon.form.gandi_openstack_password.data = account.credentials["gandi_openstack_password"]
        form.horizon.form.gandi_openstack_tenant_id.data = account.credentials["gandi_openstack_tenant_id"]
        form.max_instances.data = account.max_instances
    elif account.provider == CloudProvider.GITLAB and isinstance(form, GitlabAccountForm):
        form.gitlab_token.data = account.credentials["gitlab_token"]
    elif account.provider == CloudProvider.OVH and isinstance(form, OvhAccountForm):
        form.horizon.form.ovh_openstack_user.data = account.credentials["ovh_openstack_user"]
        form.horizon.form.ovh_openstack_password.data = account.credentials["ovh_openstack_password"]
        form.horizon.form.ovh_openstack_tenant_id.data = account.credentials["ovh_openstack_tenant_id"]
        form.ovh_api.form.ovh_cloud_application_key.data = account.credentials["ovh_cloud_application_key"]
        form.ovh_api.form.ovh_cloud_application_secret.data = account.credentials["ovh_cloud_application_secret"]
        form.ovh_api.form.ovh_cloud_consumer_key.data = account.credentials["ovh_cloud_consumer_key"]
        form.max_instances.data = account.max_instances
    else:
        raise RuntimeError(f"Unknown provider {account.provider} or form data {type(form)} did not match provider.")


@bp.route("/list")
def cloud_account_list() -> ResponseReturnValue:
    accounts: List[CloudAccount] = CloudAccount.query.filter(CloudAccount.destroyed.is_(None)).all()
    return render_template("list.html.j2",
                           title="Cloud Accounts",
                           item="cloud account",
                           items=accounts,
                           new_link=url_for("portal.cloud.cloud_account_new"),
                           **_SECTION_TEMPLATE_VARS)


@bp.route("/new", methods=['GET', 'POST'])
def cloud_account_new() -> ResponseReturnValue:
    form = NewCloudAccountForm()
    form.provider.choices = sorted([
        (provider.name, provider.description) for provider in CloudProvider
    ], key=lambda p: p[1].lower())
    if form.validate_on_submit():
        return redirect(url_for("portal.cloud.cloud_account_new_for", provider=form.provider.data))
    return render_template("new.html.j2",
                           form=form,
                           **_SECTION_TEMPLATE_VARS)


@bp.route("/new/<provider>", methods=['GET', 'POST'])
def cloud_account_new_for(provider: str) -> ResponseReturnValue:
    form = provider_forms[provider]()
    form.provider.data = CloudProvider[provider].description
    if form.validate_on_submit():
        cloud_account_save(None, CloudProvider[provider], form)
        db.session.commit()
        return redirect(url_for("portal.cloud.cloud_account_list"))
    return render_template("new.html.j2",
                           form=form,
                           **_SECTION_TEMPLATE_VARS)


@bp.route("/edit/<account_id>", methods=['GET', 'POST'])
def cloud_account_edit(account_id: int) -> ResponseReturnValue:
    account = CloudAccount.query.filter(
        CloudAccount.id == account_id,
        CloudAccount.destroyed.is_(None),
    ).first()
    if not account:
        return "Not found", 404
    form = provider_forms[account.provider.name]()
    if form.validate_on_submit():
        cloud_account_save(account, account.provider, form)
        print(account.description)
        db.session.commit()
        return redirect(url_for("portal.cloud.cloud_account_list"))
    cloud_account_populate(form, account)
    return render_template("new.html.j2",
                           form=form,
                           **_SECTION_TEMPLATE_VARS)
