from pathlib import Path
import subprocess

from ollama_client import ask
import yaml


def _build_prompt(service: str, extra: str = "") -> str:
    prompt = (
        "Output ONLY valid docker-compose YAML. No explanations. No markdown.\n\n"
        f"Generate a docker-compose file for this service: {service}"
    )
    if extra:
        prompt = f"{prompt}\n{extra}"
    return prompt


def _generate_compose(service: str, extra: str = "") -> str:
    base_prompt = (
        _build_prompt(service, extra)
    )
    retry_prompt = (
        "Previous output was invalid. Fix YAML and return valid docker-compose only."
    )

    prompt = base_prompt
    for _ in range(3):
        response = ask(prompt)
        if response.startswith("ERROR:"):
            return response
        try:
            parsed = yaml.safe_load(response)
        except yaml.YAMLError:
            prompt = f"{base_prompt}\n\n{retry_prompt}"
            continue
        if isinstance(parsed, dict) and "services" in parsed:
            return response
        prompt = f"{base_prompt}\n\n{retry_prompt}"
    return "ERROR: invalid docker-compose"


def generate_compose(service: str) -> str:
    return _generate_compose(service)


def _is_valid_compose(compose: str) -> bool:
    try:
        parsed = yaml.safe_load(compose)
    except yaml.YAMLError:
        return False
    return isinstance(parsed, dict) and "services" in parsed

def _get_compose_logs(path: Path) -> str:
    try:
        ids_result = subprocess.run(
            ["docker", "compose", "ps", "-q"],
            cwd=path,
            check=False,
            capture_output=True,
            text=True,
        )
    except Exception as exc:
        return str(exc)

    container_ids = [line.strip() for line in ids_result.stdout.splitlines() if line.strip()]
    if not container_ids:
        return ""

    parts = []
    total = 0
    limit = 2000
    for container_id in container_ids:
        try:
            logs_result = subprocess.run(
                ["docker", "logs", container_id, "--tail=50"],
                check=False,
                capture_output=True,
                text=True,
            )
        except Exception as exc:
            chunk = f"{container_id}:\n{exc}\n"
        else:
            chunk_body = logs_result.stdout or logs_result.stderr
            chunk = f"{container_id}:\n{chunk_body}\n"
        remaining = limit - total
        if remaining <= 0:
            break
        if len(chunk) > remaining:
            chunk = chunk[:remaining]
        parts.append(chunk)
        total += len(chunk)
        if total >= limit:
            break
    return "".join(parts).strip()


def _fix_compose(service: str, error: str, status: str, logs: str) -> str:
    prompt = (
        "Deployment failed.\n\n"
        f"Service: {service}\n\n"
        f"Error: {error}\n\n"
        f"Status: {status}\n\n"
        f"Logs: {logs}\n\n"
        "Fix the docker-compose YAML. Return ONLY corrected YAML."
    )
    response = ask(prompt)
    if response.startswith("ERROR:"):
        return response
    if not _is_valid_compose(response):
        return "ERROR: invalid docker-compose"
    return response


def get_service_status(path: Path) -> str:
    try:
        result = subprocess.run(
            ["docker", "compose", "ps"],
            cwd=path,
            check=False,
            capture_output=True,
            text=True,
        )
    except Exception as exc:
        return f"ERROR: {exc}"
    output = result.stdout.strip()
    if result.returncode != 0:
        error = result.stderr.strip() or output or "docker compose ps failed"
        return f"ERROR: {error}"
    return output


def _run_compose_up(path: Path) -> tuple[bool, str]:
    try:
        result = subprocess.run(
            ["docker", "compose", "up", "-d"],
            cwd=path,
            check=False,
            capture_output=True,
            text=True,
        )
    except Exception as exc:
        return False, str(exc)
    if result.returncode != 0:
        error = result.stderr.strip() or result.stdout.strip() or "docker compose up failed"
        return False, error
    return True, result.stdout.strip()


def deploy_service(service: str) -> str:
    prompt_extra = ""
    try:
        docker_ps = subprocess.run(
            ["docker", "ps"],
            check=False,
            capture_output=True,
            text=True,
        )
        docker_ps_output = docker_ps.stdout
        if ":80->" in docker_ps_output or "0.0.0.0:80-" in docker_ps_output or "[::]:80-" in docker_ps_output:
            prompt_extra = "Use a different port than 80"
    except Exception:
        docker_ps_output = ""

    compose = _generate_compose(service, prompt_extra)
    if compose.startswith("ERROR:"):
        return compose

    deployments_dir = Path("./deployments")
    target_dir = deployments_dir / service
    suffix = 1
    while target_dir.exists():
        target_dir = deployments_dir / f"{service}-{suffix}"
        suffix += 1

    target_dir.mkdir(parents=True, exist_ok=False)
    compose_path = target_dir / "docker-compose.yml"
    compose_path.write_text(compose, encoding="utf-8")

    ok, error = _run_compose_up(target_dir)
    if not ok:
        status = get_service_status(target_dir)
        logs = _get_compose_logs(target_dir)
        fixed_compose = _fix_compose(service, error, status, logs)
        if fixed_compose.startswith("ERROR:"):
            if status and not status.startswith("ERROR:"):
                return f"ERROR: {error}\n{status}"
            return f"ERROR: {error}"
        compose_path.write_text(fixed_compose, encoding="utf-8")
        ok, error = _run_compose_up(target_dir)
        if not ok:
            status = get_service_status(target_dir)
            logs = _get_compose_logs(target_dir)
            debug = f"""
ESCALATION REQUIRED

SERVICE: {service}

ERROR:
{error}

STATUS:
{status}

LOGS:
{logs}
"""
            return f"ESCALATE_TO_CODEX\n{debug}"

    status = get_service_status(target_dir)
    if status.startswith("ERROR:"):
        return status
    if "Up" not in status or "unhealthy" in status.lower():
        return f"ERROR: no running services\n{status}"

    return f"DEPLOYED: {target_dir.name}\n{status}"


if __name__ == "__main__":
    print(deploy_service("nginx"))
