import sys
from typing import Optional

import requests

from tfcommander.config import get_consul_ip
from tfcommander.exceptions import (
    ConnectionError,
    NoDomainError,
    ServiceDeregistrationError,
    ServiceRegistrationError,
)

DEFAULT_RESOLVER = "myresolver"
DEFAULT_TIMEOUT = 10


def _build_tags(
    service_id: str,
    domain_rules: str,
    port: int,
    disable_tls: bool,
    resolver_name: str,
    https_insecure: bool,
    middlewares: Optional[list[str]],
    servers_transport: Optional[str],
) -> list[str]:
    tags = ["traefik.enable=true"]

    tags.append(f"traefik.http.routers.{service_id}.rule={domain_rules}")

    if disable_tls:
        tags.append(f"traefik.http.routers.{service_id}.entrypoints=web")
        tags.append(f"traefik.http.routers.{service_id}.service={service_id}")
    else:
        tags.append(f"traefik.http.routers.{service_id}.entrypoints=websecure")
        tags.append(f"traefik.http.routers.{service_id}.tls=true")
        tags.append(
            f"traefik.http.routers.{service_id}.tls.certResolver={resolver_name}"
        )

    tags.append(
        f"traefik.http.services.{service_id}.loadbalancer.server.port={port}"
    )

    if https_insecure:
        tags.append(
            f"traefik.http.services.{service_id}.loadbalancer.server.scheme=https"
        )
        tags.append(
            f"traefik.http.services.{service_id}.loadbalancer.serverstransport=insecure-transport@file"
        )

    if middlewares:
        tags.append(
            f"traefik.http.routers.{service_id}.middlewares={','.join(middlewares)}"
        )

    if servers_transport:
        tags.append(
            f"traefik.http.services.{service_id}.loadbalancer.serversTransport={servers_transport}"
        )

    return tags


def _register_insecure_transport(internal_ip: str) -> None:
    transport_url = (
        f"http://{internal_ip}:8500/v1/agent/config/traefik/transport/insecure-transport"
    )
    payload = {
        "Name": "insecure-transport",
        "TLS": {"InsecureSkipVerify": True},
    }
    try:
        requests.put(transport_url, json=payload, timeout=DEFAULT_TIMEOUT)
    except requests.exceptions.RequestException as e:
        print(f"Warning: Could not register serversTransport: {e}")


def register_service(
    public_ip: str,
    service_id: str,
    service_name: str,
    address: str,
    port: int,
    domains: Optional[list[str]] = None,
    middlewares: Optional[list[str]] = None,
    https_insecure: bool = False,
    custom_rule: Optional[str] = None,
    disable_tls: bool = False,
    servers_transport: Optional[str] = None,
    resolver: Optional[str] = None,
) -> None:
    internal_ip = get_consul_ip(public_ip)
    resolver_name = resolver if resolver else DEFAULT_RESOLVER

    if custom_rule:
        domain_rules = custom_rule
    else:
        if not domains:
            raise NoDomainError()
        domain_rules = " || ".join([f"Host(`{d}`)" for d in domains])

    tags = _build_tags(
        service_id=service_id,
        domain_rules=domain_rules,
        port=port,
        disable_tls=disable_tls,
        resolver_name=resolver_name,
        https_insecure=https_insecure,
        middlewares=middlewares,
        servers_transport=servers_transport,
    )

    if https_insecure:
        _register_insecure_transport(internal_ip)

    payload = {
        "ID": service_id,
        "Name": service_name,
        "Address": address,
        "Port": port,
        "Tags": tags,
    }

    url = f"http://{internal_ip}:8500/v1/agent/service/register"
    try:
        resp = requests.put(url, json=payload, timeout=DEFAULT_TIMEOUT)
    except requests.exceptions.RequestException as e:
        raise ConnectionError(internal_ip, e)

    if resp.status_code != 200:
        raise ServiceRegistrationError(
            f"Failed to register service. HTTP {resp.status_code}: {resp.text}"
        )

    parts = [f"Service '{service_name}' (ID: {service_id}) registered in Consul"]
    if resolver:
        parts.append(f" (resolver: {resolver_name})")
    if middlewares:
        parts.append(f" with middlewares: {', '.join(middlewares)}")
    if servers_transport:
        parts.append(f" and serversTransport={servers_transport}")
    parts.append(".")
    print("".join(parts))


def deregister_service(public_ip: str, service_id: str) -> None:
    internal_ip = get_consul_ip(public_ip)

    transport_url = (
        f"http://{internal_ip}:8500/v1/agent/config/traefik/transport/{service_id}-transport"
    )
    try:
        resp_t = requests.delete(transport_url, timeout=DEFAULT_TIMEOUT)
        if resp_t.status_code in (200, 204):
            print(f"Removed serversTransport '{service_id}-transport'.")
    except requests.exceptions.RequestException:
        pass

    url = f"http://{internal_ip}:8500/v1/agent/service/deregister/{service_id}"
    try:
        resp = requests.put(url, timeout=DEFAULT_TIMEOUT)
    except requests.exceptions.RequestException as e:
        raise ConnectionError(internal_ip, e)

    if resp.status_code != 200:
        raise ServiceDeregistrationError(
            f"Failed to deregister service. HTTP {resp.status_code}: {resp.text}"
        )

    print(f"Service ID '{service_id}' deregistered from Consul at {internal_ip}.")


def list_services(public_ip: str) -> None:
    internal_ip = get_consul_ip(public_ip)
    url = f"http://{internal_ip}:8500/v1/agent/services"

    try:
        resp = requests.get(url, timeout=DEFAULT_TIMEOUT)
    except requests.exceptions.RequestException as e:
        raise ConnectionError(internal_ip, e)

    if resp.status_code != 200:
        raise ServiceRegistrationError(
            f"Failed to list services. HTTP {resp.status_code}: {resp.text}"
        )

    services = resp.json()
    if not services:
        print("No services found.")
        return

    print(f"Services on Consul at {internal_ip}:")
    print("-" * 67)
    for srv_id, srv_data in services.items():
        print(f"ID: {srv_id}")
        print(f"  Name: {srv_data.get('Service', '')}")
        print(f"  Address: {srv_data.get('Address', '')}")
        print(f"  Port: {srv_data.get('Port', '')}")
        print(f"  Tags: {', '.join(srv_data.get('Tags', []))}")
        print("-" * 67)
