from typing import Optional, Dict, List, Any

from phi.k8s.app.base import (
    K8sApp,
    AppVolumeType,  # noqa: F401
    ContainerContext,  # noqa: F401
    ServiceType,
    RestartPolicy,  # noqa: F401
    ImagePullPolicy,  # noqa: F401
    LoadBalancerProvider,  # noqa: F401
)
from phi.k8s.app.traefik.crds import ingressroute_crd, middleware_crd
from phi.utils.log import logger


class TraefikRouter(K8sApp):
    # -*- App Name
    name: str = "traefik"

    # -*- Image Configuration
    image_name: str = "traefik"
    image_tag: str = "v2.10"

    # -*- RBAC Configuration
    # Create a ServiceAccount, ClusterRole, and ClusterRoleBinding
    create_rbac: bool = True

    # -*- Install traefik CRDs
    # See: https://doc.traefik.io/traefik/providers/kubernetes-crd/#configuration-requirements
    install_crds: bool = True

    # -*- Traefik Configuration
    domain_name: Optional[str] = None
    # Enable Access Logs
    access_logs: bool = True
    # Traefik config file on the host
    traefik_config_file: Optional[str] = None
    # Traefik config file on the container
    traefik_config_file_container_path: str = "/etc/traefik/traefik.yaml"

    # -*- HTTP Configuration
    http_enabled: bool = False
    http_routes: Optional[List[dict]] = None
    http_container_port: int = 80
    http_service_port: int = 80
    http_node_port: Optional[int] = None
    http_key: str = "http"
    http_ingress_name: str = "http-ingress"
    forward_http_to_https: bool = False
    enable_http_proxy_protocol: bool = False
    enable_http_forward_headers: bool = False

    # -*- HTTPS Configuration
    https_enabled: bool = False
    https_routes: Optional[List[dict]] = None
    https_container_port: int = 443
    https_service_port: int = 443
    https_node_port: Optional[int] = None
    https_key: str = "https"
    https_ingress_name: str = "https-ingress"
    enable_https_proxy_protocol: bool = False
    enable_https_forward_headers: bool = False
    add_headers: Optional[Dict[str, dict]] = None

    # -*- Dashboard Configuration
    dashboard_enabled: bool = False
    dashboard_routes: Optional[List[dict]] = None
    dashboard_container_port: int = 8080
    dashboard_service_port: int = 8080
    dashboard_node_port: Optional[int] = None
    dashboard_key: str = "dashboard"
    dashboard_ingress_name: str = "dashboard-ingress"
    # The dashboard is gated behind a user:password, which is generated using
    #   htpasswd -nb user password
    # You can provide the "users:password" list as a dashboard_auth_users param
    # or as DASHBOARD_AUTH_USERS in the secrets_file
    # Using the secrets_file is recommended
    dashboard_auth_users: Optional[str] = None
    insecure_api_access: bool = False

    def get_dashboard_auth_users(self) -> Optional[str]:
        return self.dashboard_auth_users or self.get_secret_from_file("DASHBOARD_AUTH_USERS")

    def get_cr_policy_rules(self) -> List[Any]:
        from phi.k8s.create.rbac_authorization_k8s_io.v1.cluster_role import (
            PolicyRule,
        )

        return [
            PolicyRule(
                api_groups=[""],
                resources=["services", "endpoints", "secrets"],
                verbs=["get", "list", "watch"],
            ),
            PolicyRule(
                api_groups=["extensions", "networking.k8s.io"],
                resources=["ingresses", "ingressclasses"],
                verbs=["get", "list", "watch"],
            ),
            PolicyRule(
                api_groups=["extensions", "networking.k8s.io"],
                resources=["ingresses/status"],
                verbs=["update"],
            ),
            PolicyRule(
                api_groups=["traefik.io", "traefik.containo.us"],
                resources=[
                    "middlewares",
                    "middlewaretcps",
                    "ingressroutes",
                    "traefikservices",
                    "ingressroutetcps",
                    "ingressrouteudps",
                    "tlsoptions",
                    "tlsstores",
                    "serverstransports",
                ],
                verbs=["get", "list", "watch"],
            ),
        ]

    def get_container_args(self) -> Optional[List[str]]:
        if self.command is not None:
            if isinstance(self.command, str):
                return self.command.strip().split(" ")
            return self.command

        container_args = ["--providers.kubernetescrd"]

        if self.access_logs:
            container_args.append("--accesslog")

        if self.http_enabled:
            container_args.append(f"--entrypoints.{self.http_key}.Address=:{self.http_service_port}")
            if self.enable_http_proxy_protocol:
                container_args.append(f"--entrypoints.{self.http_key}.proxyProtocol.insecure=true")
            if self.enable_http_forward_headers:
                container_args.append(f"--entrypoints.{self.http_key}.forwardedHeaders.insecure=true")

        if self.https_enabled:
            container_args.append(f"--entrypoints.{self.https_key}.Address=:{self.https_service_port}")
            if self.enable_https_proxy_protocol:
                container_args.append(f"--entrypoints.{self.https_key}.proxyProtocol.insecure=true")
            if self.enable_https_forward_headers:
                container_args.append(f"--entrypoints.{self.https_key}.forwardedHeaders.insecure=true")
            if self.forward_http_to_https:
                container_args.extend(
                    [
                        f"--entrypoints.{self.http_key}.http.redirections.entryPoint.to={self.https_key}",
                        f"--entrypoints.{self.http_key}.http.redirections.entryPoint.scheme=https",
                    ]
                )

        if self.dashboard_enabled:
            container_args.append("--api=true")
            container_args.append("--api.dashboard=true")
            if self.insecure_api_access:
                container_args.append("--api.insecure")

        return container_args

    def get_secrets(self) -> List[Any]:
        return self.add_secrets or []

    def get_ports(self) -> List[Any]:
        from phi.k8s.create.common.port import CreatePort

        ports: List[CreatePort] = self.add_ports or []

        if self.http_enabled:
            web_port = CreatePort(
                name=self.http_key,
                container_port=self.http_container_port,
                service_port=self.http_service_port,
                target_port=self.http_key,
            )
            if (
                self.service_type in (ServiceType.NODE_PORT, ServiceType.LOAD_BALANCER)
                and self.http_node_port is not None
            ):
                web_port.node_port = self.http_node_port
            ports.append(web_port)

        if self.https_enabled:
            websecure_port = CreatePort(
                name=self.https_key,
                container_port=self.https_container_port,
                service_port=self.https_service_port,
                target_port=self.https_key,
            )
            if (
                self.service_type in (ServiceType.NODE_PORT, ServiceType.LOAD_BALANCER)
                and self.https_node_port is not None
            ):
                websecure_port.node_port = self.https_node_port
            ports.append(websecure_port)

        if self.dashboard_enabled:
            dashboard_port = CreatePort(
                name=self.dashboard_key,
                container_port=self.dashboard_container_port,
                service_port=self.dashboard_service_port,
                target_port=self.dashboard_key,
            )
            if (
                self.service_type in (ServiceType.NODE_PORT, ServiceType.LOAD_BALANCER)
                and self.dashboard_node_port is not None
            ):
                dashboard_port.node_port = self.dashboard_node_port
            ports.append(dashboard_port)

        return ports

    def add_app_resources(self, namespace: str, service_account_name: Optional[str]) -> List[Any]:
        from phi.k8s.create.apiextensions_k8s_io.v1.custom_object import CreateCustomObject

        app_resources = self.add_resources or []

        if self.http_enabled:
            http_ingressroute = CreateCustomObject(
                name=self.http_ingress_name,
                crd=ingressroute_crd,
                spec={
                    "entryPoints": [self.http_key],
                    "routes": self.http_routes,
                },
                app_name=self.get_app_name(),
                namespace=namespace,
            )
            app_resources.append(http_ingressroute)
            logger.debug(f"Added IngressRoute: {http_ingressroute.name}")

        if self.https_enabled:
            https_ingressroute = CreateCustomObject(
                name=self.https_ingress_name,
                crd=ingressroute_crd,
                spec={
                    "entryPoints": [self.https_key],
                    "routes": self.https_routes,
                },
                app_name=self.get_app_name(),
                namespace=namespace,
            )
            app_resources.append(https_ingressroute)
            logger.debug(f"Added IngressRoute: {https_ingressroute.name}")

        if self.add_headers:
            headers_middleware = CreateCustomObject(
                name="header-middleware",
                crd=middleware_crd,
                spec={
                    "headers": self.add_headers,
                },
                app_name=self.get_app_name(),
                namespace=namespace,
            )
            app_resources.append(headers_middleware)
            logger.debug(f"Added Middleware: {headers_middleware.name}")

        if self.dashboard_enabled:
            # create dashboard_auth_middleware if auth provided
            # ref: https://doc.traefik.io/traefik/operations/api/#configuration
            dashboard_auth_middleware = None
            dashboard_auth_users = self.get_dashboard_auth_users()
            if dashboard_auth_users is not None:
                from phi.k8s.create.core.v1.secret import CreateSecret

                dashboard_auth_secret = CreateSecret(
                    secret_name="dashboard-auth-secret",
                    app_name=self.get_app_name(),
                    namespace=namespace,
                    string_data={"users": dashboard_auth_users},
                )
                app_resources.append(dashboard_auth_secret)
                logger.debug(f"Added Secret: {dashboard_auth_secret.secret_name}")

                dashboard_auth_middleware = CreateCustomObject(
                    name="dashboard-auth-middleware",
                    crd=middleware_crd,
                    spec={"basicAuth": {"secret": dashboard_auth_secret.secret_name}},
                    app_name=self.get_app_name(),
                    namespace=namespace,
                )
                app_resources.append(dashboard_auth_middleware)
                logger.debug(f"Added Middleware: {dashboard_auth_middleware.name}")

            dashboard_routes = self.dashboard_routes
            # use default dashboard routes
            if dashboard_routes is None:
                # domain must be provided
                if self.domain_name is not None:
                    dashboard_routes = [
                        {
                            "kind": "Rule",
                            "match": f"Host(`traefik.{self.domain_name}`)",
                            "middlewares": [
                                {
                                    "name": dashboard_auth_middleware.name,
                                    "namespace": namespace,
                                },
                            ]
                            if dashboard_auth_middleware is not None
                            else [],
                            "services": [
                                {
                                    "kind": "TraefikService",
                                    "name": "api@internal",
                                }
                            ],
                        },
                    ]

            dashboard_ingressroute = CreateCustomObject(
                name=self.dashboard_ingress_name,
                crd=ingressroute_crd,
                spec={
                    # "entryPoints": [dashboard_entrypoint],
                    "routes": dashboard_routes,
                },
                app_name=self.get_app_name(),
                namespace=namespace,
            )
            app_resources.append(dashboard_ingressroute)
            logger.debug(f"Added IngressRoute: {dashboard_ingressroute.name}")

        if self.install_crds:
            from phi.k8s.resource.yaml import YamlResource

            if self.yaml_resources is None:
                self.yaml_resources = []
            self.yaml_resources.append(
                YamlResource(
                    name="traefik-crds",
                    url="https://raw.githubusercontent.com/traefik/traefik/v2.10/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml",
                )
            )
            logger.debug("Added CRD yaml")

        return app_resources
