import datetime
import os
from typing import Any

from app import app
from app.extensions import db
from app.models.base import Group
from app.models.onions import Eotk
from app.terraform import DeterministicZip
from app.terraform.eotk import eotk_configuration
from app.terraform.terraform import TerraformAutomation


def update_eotk_instance(group_id: int,
                         region: str,
                         instance_id: str) -> None:
    instance = Eotk.query.filter(
        Eotk.group_id == group_id,
        Eotk.region == region,
        Eotk.provider == "aws",
        Eotk.destroyed.is_(None)
    ).first()
    if instance is None:
        instance = Eotk()
        instance.added = datetime.datetime.utcnow()
        instance.group_id = group_id
        instance.provider = "aws"
        instance.region = region
        db.session.add(instance)
    instance.updated = datetime.datetime.utcnow()
    instance.instance_id = instance_id


class EotkAWSAutomation(TerraformAutomation):
    short_name = "eotk_aws"
    description = "Deploy EOTK instances to AWS"

    template_parameters = [
        "aws_access_key",
        "aws_secret_key"
    ]

    template = """
    terraform {
      {{ backend_config }}
      required_providers {
        aws = {
          version = "~> 4.4.0"
        }
      }
    }

    provider "aws" {
      access_key = "{{ aws_access_key }}"
      secret_key = "{{ aws_secret_key }}"
      region = "us-east-2"
    }

    {% for group in groups %}
    module "eotk_{{ group.id }}" {
      source = "{{ terraform_modules_path }}/terraform-aws-bc-eotk"
      namespace = "{{ global_namespace }}"
      tenant = "{{ group.group_name }}"
      name = "eotk"
      label_order = ["namespace", "tenant", "name", "attributes"]
      tags = {
        "Application" = "EOTK"
      }
      configuration_bundle = "{{ group.id }}.zip"
    }

    output "eotk_instances_{{ group.id }}" {
      value = module.eotk_{{ group.id }}.instances
    }
    {% endfor %}
    """

    def tf_generate(self) -> None:
        if not self.working_dir:
            raise RuntimeError("No working directory specified.")
        self.tf_write(
            self.template,
            groups=Group.query.filter(
                Group.eotk.is_(True),
                Group.destroyed.is_(None)
            ).all(),
            global_namespace=app.config['GLOBAL_NAMESPACE'],
            terraform_modules_path=os.path.join(*list(os.path.split(app.root_path))[:-1], 'terraform-modules'),
            backend_config=f"""backend "http" {{
              lock_address = "{app.config['TFSTATE_BACKEND']}/{self.short_name}"
              unlock_address = "{app.config['TFSTATE_BACKEND']}/{self.short_name}"
              address = "{app.config['TFSTATE_BACKEND']}/{self.short_name}"
            }}""",
            **{
                k: app.config[k.upper()]
                for k in self.template_parameters
            }
        )
        for group in Group.query.filter(
                Group.eotk.is_(True),
                Group.destroyed.is_(None)
        ).order_by(Group.id).all():
            with DeterministicZip(os.path.join(self.working_dir, f"{group.id}.zip")) as dzip:
                dzip.add_file("sites.conf", eotk_configuration(group).encode('utf-8'))
                for onion in sorted(group.onions, key=lambda o: o.onion_name):
                    dzip.add_file(f"{onion.onion_name}.v3pub.key", onion.onion_public_key)
                    dzip.add_file(f"{onion.onion_name}.v3sec.key", onion.onion_private_key)
                    dzip.add_file(f"{onion.onion_name[:20]}-v3.cert", onion.tls_public_key)
                    dzip.add_file(f"{onion.onion_name[:20]}-v3.pem", onion.tls_private_key)

    def tf_posthook(self, *, prehook_result: Any = None) -> None:
        for e in Eotk.query.all():
            db.session.delete(e)
        outputs = self.tf_output()
        for output in outputs:
            if output.startswith("eotk_instances_"):
                try:
                    group_id = int(output[len("eotk_instance_") + 1:])
                    for az in outputs[output]['value']:
                        update_eotk_instance(group_id, az, outputs[output]['value'][az])
                except ValueError:
                    pass
        db.session.commit()
