import json
from enum import Enum
from typing import Optional, Tuple, Dict

import typer
from rich import box
from rich.table import Table, Column

from pyrrowhead import rich_console
from pyrrowhead.management.serviceregistry import grouped_services
from pyrrowhead.management.utils import get_service, post_service, delete_service
from pyrrowhead.management.authorization import add_authorization_rule
from pyrrowhead.utils import (
    get_core_system_address_and_port,
    get_active_cloud_directory,
)


class SortbyChoices(str, Enum):
    RULE_ID = "id"
    PRIORITY = "priority"
    CONSUMER = "consumer"
    PROVIDER = "provider"
    SERVICE = "service"


def get_ids_from_service_definition(
    service_definition: str,
    interface_name: str,
    provider_name: str,
    address: str,
    port: int,
) -> Tuple[int, int, int]:
    """
    Args:
        service_definition:
        interface_name:
        provider_name:
        address:
        port:

    Returns:
        Tuple of service definition id, interface id, and provider id
    """
    response_data, status = grouped_services()

    services_by_definition = response_data.get("servicesGroupedByServiceDefinition")
    for service_definition_entry in services_by_definition:
        if service_definition_entry["serviceDefinition"] == service_definition:
            for service in service_definition_entry["providerServices"]:
                if (
                    service["provider"]["systemName"] == provider_name
                    and service["provider"]["address"] == address
                    and service["provider"]["port"] == port
                ):
                    correct_service = service
                    break
            else:
                rich_console.print(
                    f"Could not find provider {provider_name}@{address}:{port} for "
                    f"any service with service definition {service_definition}."
                )
                raise typer.Exit()
            break
    else:
        rich_console.print(
            f"Could not find any services with service definition {service_definition}."
        )
        raise typer.Exit()
    service_definition_id = correct_service["serviceDefinition"]["id"]
    provider_id = correct_service["provider"]["id"]
    interfaces = correct_service["interfaces"]
    for interface in interfaces:
        if interface["interfaceName"] == interface_name:
            interface_id = interface["id"]
            break
    else:
        rich_console.print(
            f"Could not find interface {interface_name} for service "
            f"{service_definition} in provider {provider_name}@{address}:{port}, "
            f"available interfaces are "
            f'{", ".join(interface["interfaceName"] for interface in interfaces)}'
        )
        raise typer.Exit()
    return service_definition_id, interface_id, provider_id


def table_sort(rule, choice):
    if choice == SortbyChoices.RULE_ID:
        return rule["id"]
    elif choice == SortbyChoices.PRIORITY:
        return rule["priority"]
    elif choice == SortbyChoices.CONSUMER:
        return rule["consumerSystem"]["id"]
    elif choice == SortbyChoices.PROVIDER:
        return rule["providerSystem"]["id"]
    elif choice == SortbyChoices.SERVICE:
        return rule["serviceDefinition"]["id"]
    else:
        raise RuntimeError(f"Invalid sorting choice: {choice}")


def table_condition(
    orch_rule: Dict,
    service_definition: Optional[str],
    consumer_id: Optional[int],
    consumer_name: Optional[str],
    provider_id: Optional[int],
    provider_name: Optional[str],
):
    if service_definition is not None:
        return orch_rule["serviceDefinition"]["serviceDefinition"] == service_definition
    elif consumer_id is not None:
        breakpoint()
        return orch_rule["consumerSystem"]["id"] == consumer_id
    elif consumer_name is not None:
        return orch_rule["consumerSystem"]["systemName"] == consumer_name
    elif provider_id is not None:
        return orch_rule["providerSystem"]["id"] == provider_id
    elif provider_name is not None:
        return orch_rule["providerSystem"]["systemName"] == provider_name
    else:
        return True


def create_orchestration_table(
    response_data: Dict,
    service_definition: Optional[str],
    consumer_id: Optional[int],
    consumer_name: Optional[str],
    provider_id: Optional[int],
    provider_name: Optional[str],
    sort_by: str,
):
    table = Table(
        Column(header="id", style="red"),
        Column(header="Consumer (id)", style="bright_blue"),
        Column(style="bright_blue"),
        Column(header="Provider (id)", style="blue"),
        Column(style="blue"),
        Column(header="Service Definition (id)", style="green"),
        Column(style="green"),
        Column(header="Interface (id)", style="bright_yellow"),
        Column(style="bright_yellow"),
        Column(header="priority", style="bright_white"),
        box=box.HORIZONTALS,
    )

    for orch_rule in sorted(
        response_data["data"], key=lambda x: table_sort(x, sort_by)
    ):
        if not table_condition(
            orch_rule,
            service_definition,
            consumer_id,
            consumer_name,
            provider_id,
            provider_name,
        ):
            continue
        table.add_row(
            str(orch_rule["id"]),
            f'{orch_rule["consumerSystem"]["systemName"]}',
            f'({orch_rule["consumerSystem"]["id"]})',
            f'{orch_rule["providerSystem"]["systemName"]}',
            f'({orch_rule["providerSystem"]["id"]})',
            f'{orch_rule["serviceDefinition"]["serviceDefinition"]}',
            f'({orch_rule["serviceDefinition"]["id"]})',
            f'{orch_rule["serviceInterface"]["interfaceName"]}',
            f'({orch_rule["serviceInterface"]["id"]})',
            str(orch_rule["priority"]),
        )

    return table


def list_orchestration_rules():
    active_cloud_directory = get_active_cloud_directory()
    address, port, secure, scheme = get_core_system_address_and_port(
        "orchestrator",
        active_cloud_directory,
    )
    scheme = "https" if secure else "http"
    response = get_service(
        f"{scheme}://{address}:{port}/orchestrator/mgmt/store",
        active_cloud_directory,
    )
    return response.json(), response.status_code


def add_orchestration_rule(
    service_definition: str,
    service_interface: str,
    provider_system: Tuple[str, str, int],
    consumer_id: Optional[int] = None,
    priority: int = 1,
    metadata: Optional[int] = None,
    add_auth_rule: Optional[bool] = None,
):
    active_cloud_directory = get_active_cloud_directory()
    address, port, secure, scheme = get_core_system_address_and_port(
        "orchestrator",
        active_cloud_directory,
    )
    # cloud_name = active_cloud_directory.name
    # org_name = active_cloud_directory.parents[0].name

    orchestration_input = [
        {
            "serviceDefinitionName": service_definition,
            "serviceInterfaceName": service_interface,
            "consumerSystemId": consumer_id,
            "providerSystem": dict(
                zip(("systemName", "address", "port"), provider_system)
            ),
            # "cloud": {"operator": org_name, "name": OPT_CLOUD_NAME, "secure": secure},
            "priority": priority,
            "attribute": metadata,
        }
    ]

    import pprint

    pprint.pprint(orchestration_input)
    response = post_service(
        f"{scheme}://{address}:{port}/orchestrator/mgmt/store",
        active_cloud_directory,
        json=orchestration_input,
    )

    if add_auth_rule:
        (
            service_definition_id,
            interface_id,
            provider_id,
        ) = get_ids_from_service_definition(
            service_definition, service_interface, *provider_system
        )
        add_authorization_rule(
            consumer_id,  # type: ignore
            provider_id,
            interface_id,
            service_definition_id
            # TODO: mypy complains here, remove ignore and fix later
        )

    try:
        response_data = response.json()
    except json.JSONDecodeError:
        response_data = {"errorMessage": "Error decoding json."}

    return response_data, response.status_code


def remove_orchestration_rule(orchestration_id):
    active_cloud_directory = get_active_cloud_directory()
    address, port, secure, scheme = get_core_system_address_and_port(
        "orchestrator",
        active_cloud_directory,
    )
    response = delete_service(
        f"{scheme}://{address}:{port}/orchestrator/mgmt/store/{orchestration_id}",
        active_cloud_directory,
    )

    try:
        response_data = response.json()
    except json.JSONDecodeError:
        response_data = {
            "errorMessage": "Could not decode orchestration "
            "response with status code {response.status_code"
        }
    return response_data, response.status_code
