"""Tests for DeployService.deploy with --image."""

from __future__ import annotations

import json

import pytest

from kuberoku.exceptions import AppNotFoundError
from kuberoku.factory import KuberokuFactory
from kuberoku.k8s.resources import safe_name
from tests.backends.fake import FakeNotFoundError


def test_first_deploy_creates_deployment(app_factory: KuberokuFactory) -> None:
    release = app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    dep = app_factory.k8s.get_deployment(
        app_factory.namespace, safe_name(app_factory.prefix, "myapp", "web")
    )
    assert dep["spec"]["template"]["spec"]["containers"][0]["image"] == "myapp:v1"
    assert release.version == 1


def test_first_deploy_creates_service_for_web(app_factory: KuberokuFactory) -> None:
    app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    svc = app_factory.k8s.get_service(
        app_factory.namespace, safe_name(app_factory.prefix, "myapp", "web")
    )
    assert svc["spec"]["type"] == "ClusterIP"


def test_default_web_process(app_factory: KuberokuFactory) -> None:
    release = app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    assert "web" in release.images


def test_subsequent_deploy_patches_image(app_factory: KuberokuFactory) -> None:
    app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    app_factory.deploy.deploy("myapp", image="myapp:v2", wait=False)
    dep = app_factory.k8s.get_deployment(
        app_factory.namespace, safe_name(app_factory.prefix, "myapp", "web")
    )
    assert dep["spec"]["template"]["spec"]["containers"][0]["image"] == "myapp:v2"


def test_deploy_preserves_replicas(app_factory: KuberokuFactory) -> None:
    app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    # Manually scale up
    dep_name = safe_name(app_factory.prefix, "myapp", "web")
    dep = app_factory.k8s.get_deployment(app_factory.namespace, dep_name)
    dep["spec"]["replicas"] = 5
    app_factory.k8s.update_deployment(app_factory.namespace, dep_name, dep)
    # Redeploy
    app_factory.deploy.deploy("myapp", image="myapp:v2", wait=False)
    dep = app_factory.k8s.get_deployment(app_factory.namespace, dep_name)
    assert dep["spec"]["replicas"] == 5


def test_deploy_specific_type(app_factory: KuberokuFactory) -> None:
    release = app_factory.deploy.deploy(
        "myapp", image="myapp:v1", process_type="worker", wait=False
    )
    assert "worker" in release.images
    dep = app_factory.k8s.get_deployment(
        app_factory.namespace, safe_name(app_factory.prefix, "myapp", "worker")
    )
    assert dep["spec"]["template"]["spec"]["containers"][0]["image"] == "myapp:v1"


def test_worker_gets_no_service(app_factory: KuberokuFactory) -> None:
    app_factory.deploy.deploy("myapp", image="myapp:v1", process_type="worker", wait=False)
    with pytest.raises(FakeNotFoundError):
        app_factory.k8s.get_service(
            app_factory.namespace, safe_name(app_factory.prefix, "myapp", "worker")
        )


def test_custom_ports(app_factory: KuberokuFactory) -> None:
    ports = [{"containerPort": 3000, "protocol": "TCP"}]
    app_factory.deploy.deploy("myapp", image="myapp:v1", ports=ports, wait=False)
    dep = app_factory.k8s.get_deployment(
        app_factory.namespace, safe_name(app_factory.prefix, "myapp", "web")
    )
    assert dep["spec"]["template"]["spec"]["containers"][0]["ports"] == ports


def test_multi_port_deploy(app_factory: KuberokuFactory) -> None:
    """Deploy with multiple ports on the same process type (e.g., SMTP 25+587)."""
    ports = [
        {"containerPort": 25, "protocol": "TCP"},
        {"containerPort": 587, "protocol": "TCP"},
    ]
    app_factory.deploy.deploy(
        "myapp",
        image="myapp:v1",
        process_type="smtp",
        ports=ports,
        wait=False,
    )
    ns = app_factory.namespace
    prefix = app_factory.prefix

    # Deployment has both ports
    dep = app_factory.k8s.get_deployment(ns, safe_name(prefix, "myapp", "smtp"))
    container_ports = dep["spec"]["template"]["spec"]["containers"][0]["ports"]
    assert len(container_ports) == 2
    assert container_ports[0]["containerPort"] == 25
    assert container_ports[1]["containerPort"] == 587

    # Service has both ports
    svc = app_factory.k8s.get_service(ns, safe_name(prefix, "myapp", "smtp"))
    svc_ports = svc["spec"]["ports"]
    assert len(svc_ports) == 2
    assert {p["port"] for p in svc_ports} == {25, 587}

    # NetworkPolicy allows both ports
    np_name = safe_name(prefix, "netpol-allow", "myapp-smtp")
    np = app_factory.k8s.get_network_policy(ns, np_name)
    np_ports = np["spec"]["ingress"][0]["ports"]
    assert len(np_ports) == 2
    assert {p["port"] for p in np_ports} == {25, 587}


def test_multi_type_different_ports(app_factory: KuberokuFactory) -> None:
    """Different process types can have different ports (same image, Heroku model)."""
    web_ports = [{"containerPort": 8080, "protocol": "TCP"}]
    smtp_ports = [
        {"containerPort": 25, "protocol": "TCP"},
        {"containerPort": 587, "protocol": "TCP"},
    ]
    app_factory.deploy.deploy(
        "myapp",
        image="myapp:v1",
        process_type="web",
        ports=web_ports,
        wait=False,
    )
    app_factory.deploy.deploy(
        "myapp",
        image="myapp:v1",
        process_type="smtp",
        ports=smtp_ports,
        wait=False,
    )
    ns = app_factory.namespace
    prefix = app_factory.prefix

    # web has 1 port
    web_dep = app_factory.k8s.get_deployment(ns, safe_name(prefix, "myapp", "web"))
    assert len(web_dep["spec"]["template"]["spec"]["containers"][0]["ports"]) == 1

    # smtp has 2 ports
    smtp_dep = app_factory.k8s.get_deployment(ns, safe_name(prefix, "myapp", "smtp"))
    assert len(smtp_dep["spec"]["template"]["spec"]["containers"][0]["ports"]) == 2

    # Both have separate Services
    web_svc = app_factory.k8s.get_service(ns, safe_name(prefix, "myapp", "web"))
    assert len(web_svc["spec"]["ports"]) == 1
    smtp_svc = app_factory.k8s.get_service(ns, safe_name(prefix, "myapp", "smtp"))
    assert len(smtp_svc["spec"]["ports"]) == 2


def test_release_records_all_images(app_factory: KuberokuFactory) -> None:
    # Same image for both types (Heroku model)
    app_factory.deploy.deploy("myapp", image="myapp:v1", process_type="web", wait=False)
    app_factory.deploy.deploy("myapp", image="myapp:v1", process_type="worker", wait=False)
    # Redeploy just web with v2 — release should still have worker at v1
    release = app_factory.deploy.deploy("myapp", image="myapp:v2", process_type="web", wait=False)
    assert release.images["web"] == "myapp:v2"
    assert release.images["worker"] == "myapp:v1"


def test_deploy_creates_network_policy(app_factory: KuberokuFactory) -> None:
    app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    np_name = safe_name(app_factory.prefix, "netpol-allow", "myapp-web")
    np = app_factory.k8s.get_network_policy(app_factory.namespace, np_name)
    assert np["spec"]["policyTypes"] == ["Ingress"]


def test_deploy_on_step(app_factory: KuberokuFactory) -> None:
    steps: list[str] = []
    app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False, on_step=steps.append)
    assert len(steps) > 0


def test_deploy_app_not_found(app_factory: KuberokuFactory) -> None:
    with pytest.raises(AppNotFoundError):
        app_factory.deploy.deploy("nonexistent", image="myapp:v1", wait=False)


def test_deploy_idempotent(app_factory: KuberokuFactory) -> None:
    app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    dep = app_factory.k8s.get_deployment(
        app_factory.namespace, safe_name(app_factory.prefix, "myapp", "web")
    )
    assert dep["spec"]["template"]["spec"]["containers"][0]["image"] == "myapp:v1"


def test_replicas_only_on_first_deploy(app_factory: KuberokuFactory) -> None:
    app_factory.deploy.deploy("myapp", image="myapp:v1", replicas=3, wait=False)
    dep_name = safe_name(app_factory.prefix, "myapp", "web")
    dep = app_factory.k8s.get_deployment(app_factory.namespace, dep_name)
    assert dep["spec"]["replicas"] == 3
    # Second deploy with replicas=5 should NOT change replicas (preserves)
    app_factory.deploy.deploy("myapp", image="myapp:v2", replicas=5, wait=False)
    dep = app_factory.k8s.get_deployment(app_factory.namespace, dep_name)
    assert dep["spec"]["replicas"] == 3


def test_deploy_preserves_resources(app_factory: KuberokuFactory) -> None:
    app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    # Set resources manually
    dep_name = safe_name(app_factory.prefix, "myapp", "web")
    dep = app_factory.k8s.get_deployment(app_factory.namespace, dep_name)
    dep["spec"]["template"]["spec"]["containers"][0]["resources"] = {
        "requests": {"cpu": "250m", "memory": "256Mi"}
    }
    app_factory.k8s.update_deployment(app_factory.namespace, dep_name, dep)
    # Redeploy
    app_factory.deploy.deploy("myapp", image="myapp:v2", wait=False)
    dep = app_factory.k8s.get_deployment(app_factory.namespace, dep_name)
    assert dep["spec"]["template"]["spec"]["containers"][0]["resources"] == {
        "requests": {"cpu": "250m", "memory": "256Mi"}
    }


def test_deploy_updates_formation_configmap(app_factory: KuberokuFactory) -> None:
    app_factory.deploy.deploy("myapp", image="myapp:v1", wait=False)
    cm = app_factory.k8s.get_configmap(
        app_factory.namespace, safe_name(app_factory.prefix, "formation", "myapp")
    )
    assert "web" in cm["data"]
    entry = json.loads(cm["data"]["web"])
    assert entry["replicas"] == 1
