#!/usr/bin/env python
import http.client
import json
import shutil
import subprocess
import time
from functools import lru_cache
from typing import List, Optional

from wasabi import msg

from ..ui import open_url


@lru_cache()
def check_gcloud_exists():
    if shutil.which("gcloud") is None:
        gcloud_url = "https://cloud.google.com/sdk/docs/install"
        msg.fail("gcloud has to be installed first")
        msg.info(f"You can install it following the instructions at: {gcloud_url}")
        msg.info("Trying to open it in 5 seconds...")
        time.sleep(5)
        open_url(gcloud_url)
        msg.fail("Please install gcloud", exits=1)


def ensure_beta():
    check_gcloud_exists()
    msg.info("Ensuring gcloud beta is installed")
    result = subprocess.run(
        ["gcloud", "components", "install", "--quiet", "beta"],
    )
    if result.returncode != 0:
        msg.fail("There was an error installing gcloud beta", exits=1)
    msg.good("Done")


def get_client_email(*, service_account_name: str, project_name: str):
    client_email = f"{service_account_name}@{project_name}.iam.gserviceaccount.com"
    return client_email


def create_project(
    project_id: str, project_name: Optional[str] = None, exist_ok=False
) -> str:
    """
    Create a Google Cloud project to use to deploy a Prodigy Teams cluster. \
    """
    check_gcloud_exists()
    if project_name is None:
        project_name = project_id
    result = subprocess.run(
        ["gcloud", "projects", "list", "--format", "json"],
        check=True,
        capture_output=True,
    )
    content = json.loads(result.stdout)
    ids = {p["projectId"] for p in content}
    if project_id in ids:
        if exist_ok:
            msg.info(f"Project {project_id} exists. Using it")
            return project_id
        else:
            msg.fail(f"Project ID {project_id} already exists", exits=1)
    msg.info(f"Creating Google Cloud project with ID: {project_id}")
    result = subprocess.run(
        [
            "gcloud",
            "projects",
            "create",
            project_id,
            "--name",
            project_name,
            "--enable-cloud-apis",
        ],
    )
    if result.returncode != 0:
        msg.fail("There was an error creating the Google Cloud project", exits=1)
    msg.good("Done")
    return project_id


def create_service_account(
    project_name: str, service_account_name: str, display_name: str, exist_ok=True
) -> str:
    """
    Create a service account that will be used to deploy the Prodigy Teams cluster. \
        Included in the init sub-command.
    """
    check_gcloud_exists()
    result = subprocess.run(
        [
            "gcloud",
            "iam",
            "service-accounts",
            "list",
            "--format",
            "json",
            "--project",
            project_name,
        ],
        check=True,
        capture_output=True,
    )
    content = json.loads(result.stdout)
    sa_names = {sa["email"].split("@")[0] for sa in content}
    if service_account_name in sa_names:
        if exist_ok:
            msg.info(f"Service account {service_account_name} exists. Using it")
            return service_account_name
        else:
            msg.fail(f"Service account {service_account_name} exists", exits=1)
    msg.info(f"Creating service account: {service_account_name}")
    result = subprocess.run(
        [
            "gcloud",
            "iam",
            "service-accounts",
            "create",
            service_account_name,
            "--display-name",
            display_name,
            "--project",
            project_name,
        ],
    )
    if result.returncode != 0:
        msg.fail("There was an error creating the service account", exits=1)
    msg.good("Done")
    return service_account_name


def add_service_account_member(project_name: str, service_account_name: str):
    """
    Add a service account as the owner of the project. \
        Included in the init sub-command.

    This is needed to be able use the service account to deploy the \
        Prodigy Teams cluster.
    """
    check_gcloud_exists()
    msg.info(
        "Adding service account membership "
        f"for: {service_account_name} to project: {project_name}"
    )
    client_email = get_client_email(
        service_account_name=service_account_name, project_name=project_name
    )
    result = subprocess.run(
        [
            "gcloud",
            "projects",
            "add-iam-policy-binding",
            project_name,
            "--member",
            f"serviceAccount:{client_email}",
            "--role",
            "roles/owner",
        ],
    )
    if result.returncode != 0:
        msg.fail("There was an error adding the service account membership", exits=1)
    msg.good("Done")
    return service_account_name


def get_billing_account():
    check_gcloud_exists()
    result = subprocess.run(
        [
            "gcloud",
            "beta",
            "billing",
            "accounts",
            "list",
            "--format",
            "value(ACCOUNT_ID)",
            "--limit=1",
        ],
        encoding="utf-8",
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    if result.returncode != 0:
        print(result.stdout)
        print(result.stderr)
        msg.fail("There was an error getting billing accounts", exits=1)
    billing_account_id = result.stdout
    return billing_account_id.strip()


def setup_billing(project_name: str) -> None:
    """
    Set up billing for the project. Included in the init sub-command.
    """
    check_gcloud_exists()
    msg.info(f"Setting up billing account for project {project_name}")
    ensure_beta()
    while True:
        billing_account_id = get_billing_account()
        if billing_account_id:
            break
        else:
            billing_url = (
                f"https://console.cloud.google.com/billing?project={project_name}"
            )
            msg.info(
                f"Before enabling the GCP APIs you have to enable billing for "
                f"the project {project_name}"
            )
            msg.info(f"To do so visit {billing_url} and link a billing account")
            msg.info("Trying to open it in 5 seconds...")
            time.sleep(5)
            open_url(billing_url)
    msg.info(
        f"Enabling billing account id {billing_account_id} for project {project_name}"
    )
    result = subprocess.run(
        [
            "gcloud",
            "beta",
            "billing",
            "projects",
            "link",
            project_name,
            "--billing-account",
            billing_account_id,
        ],
        encoding="utf-8",
    )
    if result.returncode != 0:
        msg.fail("There was an error getting billing accounts", exits=1)
    msg.good("Done")


def enable_apis(project_name: str) -> None:
    """
    Enable the required Google Cloud APIs to deploy the Prodigy Teams cluster. \
        Included in the init sub-command.
    """
    check_gcloud_exists()
    msg.info(f"Enabling cloud APIs for project {project_name}")
    msg.info(f"Enabling cloudresourcemanager for project {project_name}")
    apis = [
        "cloudkms.googleapis.com",
        "compute.googleapis.com",
        "cloudresourcemanager.googleapis.com",
        "sqladmin.googleapis.com",
        "artifactregistry.googleapis.com",
        "servicenetworking.googleapis.com",
        "iam.googleapis.com",
        "secretmanager.googleapis.com",
        "container.googleapis.com",
        "file.googleapis.com",
    ]
    for api in apis:
        msg.info(f"Enabling {api} for project {project_name}")
        result = subprocess.run(
            [
                "gcloud",
                "services",
                "enable",
                api,
                "--project",
                project_name,
            ],
        )
        if result.returncode != 0:
            msg.fail("There was an error enabling cloudresourcemanager", exits=1)
        msg.good("Done")


def create_backend_bucket(project_id: str) -> None:
    """Create GCS buckets to use for terraform."""
    result = subprocess.run(
        [
            "gsutil",
            "mb",
            "-p",
            project_id,
            f"gs://{project_id}-terraform",
        ]
    )
    if result.returncode != 0:
        msg.fail("There was an error making the terraform bucket", exits=1)


def create_terraform_cloud_workspaces(project_name: str) -> None:
    """Create and configure workspaces on terraform cloud for
    the project.

    While workspaces are created automatically, we want to
    create it ourselves so that we can change it to local
    execution.
    """
    token = get_terraform_token()
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/vnd.api+json",
    }
    for workspace in ["state-stores", "cluster"]:
        data = {
            "type": "workspaces",
            "name": f"{project_name}-{workspace}",
            "description": "",
            "execution-mode": "local",
        }
        conn = http.client.HTTPSConnection("app.terraform.io")
        conn.request(
            "POST",
            "/api/v2/organizations/explosion/workspaces",
            body=json.dumps(data, indent=2),
            headers=headers,
        )
        r = conn.getresponse()
        if r.status != 200:
            raise Exception(r.status, r.reason)


def get_terraform_token() -> str:
    msg.fail(
        (
            "Using terraform backend not supported currently: "
            "need to authenticate request to create workspace."
        ),
        exits=1,
    )


def get_default_organization() -> None:
    result = subprocess.run(["gcloud", "organizations", "list", "--format", "json"])
    if result.returncode != 0:
        msg.fail("There was an error enabling compute", exits=1)
    data = json.loads(result.stdout)
    return data["name"]


def create_ip_address(project: str, name: str) -> str:
    result = subprocess.run(
        [
            "gcloud",
            "compute",
            "addresses",
            "create",
            name,
            "--global",
            "--project",
            project,
            "--format",
            "json",
        ],
        text=True,
    )
    if result.returncode != 0:
        msg.fail("Could not assign IP address", exits=1)
    result = subprocess.run(
        [
            "gcloud",
            "compute",
            "addresses",
            "describe",
            name,
            "--global",
            "--project",
            project,
            "--format",
            "json",
        ],
        text=True,
        capture_output=True,
    )
    if result.returncode != 0:
        msg.fail(
            f"Error retrieving IP address after creation: {result.stderr}", exits=1
        )
    data = json.loads(result.stdout)
    return data["address"]


def add_owners(project_id: str, owners: List[str]) -> None:
    for owner in owners:
        if "@" not in owner:
            owner = f"{owner}@explosion.ai"
        result = subprocess.run(
            [
                "gcloud",
                "projects",
                "add-iam-policy-binding",
                project_id,
                "--member",
                f"user:{owner}",
                "--role",
                "roles/owner",
            ],
            text=True,
            capture_output=True,
        )
        if result.returncode != 0:
            msg.fail(
                f"Error assigning owner {owner} to project {project_id}: {result.stderr}",
                exits=1,
            )
