"""Lesson 4: Services in ONA Automations."""

from __future__ import annotations

from cde_onboard.lessons.base import BaseLesson
from cde_onboard.models import CodeBlock, LessonMeta, LessonStep, ScaffoldFile


class ServicesLesson(BaseLesson):
    """Teaches ONA service definitions, health checks, Docker Compose, and graceful shutdown."""

    @classmethod
    def meta(cls) -> LessonMeta:
        return LessonMeta(
            id="services",
            number=4,
            title="Services",
            description="Configure long-running services with health checks and Docker Compose.",
            overview="Define services in automations.yaml, set up health checks, configure "
            "Docker Compose with network_mode: host, and handle graceful shutdown.",
        )

    def steps(self) -> list[LessonStep]:
        return [
            LessonStep(
                title="What Are Services?",
                explanation=(
                    "**Services** are long-running processes like databases, caches, and message "
                    "brokers. Unlike tasks (which run and complete), services start and remain "
                    "running throughout your development session.\n\n"
                    "Services are defined in `.ona/automations.yaml` under the `services:` key."
                ),
            ),
            LessonStep(
                title="Defining a Service",
                explanation=(
                    "Services use a **nested** `commands:` structure with `start`, `ready`, and "
                    "optionally `stop` fields. This is different from tasks, which use a flat "
                    "`command:` field."
                ),
                code=CodeBlock(
                    language="yaml",
                    source=(
                        "services:\n"
                        "  postgres:\n"
                        "    name: PostgreSQL Database\n"
                        "    description: PostgreSQL 16 via Docker Compose\n"
                        "    triggeredBy:\n"
                        "      - postDevcontainerStart\n"
                        "    commands:\n"
                        "      start: docker compose up -d postgres\n"
                        "      ready: pg_isready -h localhost -p 5432\n"
                        "      stop: docker compose stop postgres"
                    ),
                    filename=".ona/automations.yaml",
                ),
                why=(
                    "The nested `commands:` → `start:`, `ready:`, `stop:` structure is specific "
                    "to services. ONA uses this to manage the service lifecycle. The `ready` "
                    "command is polled until it succeeds, signaling that the service is healthy."
                ),
            ),
            LessonStep(
                title="Health Checks",
                explanation=(
                    "The `commands.ready` field defines a health check command. ONA polls this "
                    "command until it returns a zero exit code, indicating the service is "
                    "ready to accept connections.\n\n"
                    "Common health check commands:"
                ),
                code=CodeBlock(
                    language="yaml",
                    source=(
                        "# PostgreSQL\n"
                        "ready: pg_isready -h localhost -p 5432\n"
                        "\n"
                        "# Redis\n"
                        "ready: redis-cli ping\n"
                        "\n"
                        "# HTTP service\n"
                        "ready: curl -sf http://localhost:3000/health\n"
                        "\n"
                        "# MySQL\n"
                        "ready: mysqladmin ping -h localhost"
                    ),
                ),
                why=(
                    "Health checks are essential because tasks with `dependsOn` wait for the "
                    "service's `ready` command to succeed. Without a proper health check, "
                    "dependent tasks might start before the service is actually ready."
                ),
            ),
            LessonStep(
                title="Docker Compose with network_mode: host",
                explanation=(
                    "Most ONA services use Docker Compose. **Critical rule**: all Docker Compose "
                    "services **must** use `network_mode: host`.\n\n"
                    "This makes services accessible on `localhost` from your dev container, "
                    "which is how ONA networking works."
                ),
                code=CodeBlock(
                    language="yaml",
                    source=(
                        "# docker-compose.yaml\n"
                        "services:\n"
                        "  postgres:\n"
                        "    image: postgres:16\n"
                        "    network_mode: host    # REQUIRED for ONA\n"
                        "    environment:\n"
                        "      POSTGRES_PASSWORD: postgres\n"
                        "      POSTGRES_DB: myapp_dev\n"
                        "\n"
                        "  redis:\n"
                        "    image: redis:7-alpine\n"
                        "    network_mode: host    # REQUIRED for ONA"
                    ),
                    filename="docker-compose.yaml",
                ),
                why=(
                    "`network_mode: host` is required because ONA environments use host "
                    "networking. Without it, services would be isolated in Docker's default "
                    "bridge network and unreachable from your application."
                ),
            ),
            LessonStep(
                title="Graceful Shutdown",
                explanation=(
                    "The optional `commands.stop` field defines how to gracefully stop a "
                    "service. If not provided, ONA sends SIGTERM. For Docker Compose services, "
                    "`docker compose stop <service>` (or `docker compose down` for all services) "
                    "is the standard approach."
                ),
                code=CodeBlock(
                    language="yaml",
                    source=(
                        "services:\n"
                        "  postgres:\n"
                        "    commands:\n"
                        "      start: docker compose up -d postgres\n"
                        "      ready: pg_isready -h localhost -p 5432\n"
                        "      stop: docker compose stop postgres"
                    ),
                    filename=".ona/automations.yaml",
                ),
            ),
            LessonStep(
                title="Full Services + Tasks Example",
                explanation=(
                    "Here's a complete automations.yaml combining services and tasks. "
                    "Click **Scaffold & Complete** to create both the automations.yaml and "
                    "docker-compose.yaml files."
                ),
                code=CodeBlock(
                    language="yaml",
                    source=(
                        "services:\n"
                        "  postgres:\n"
                        "    name: PostgreSQL Database\n"
                        "    triggeredBy:\n"
                        "      - postDevcontainerStart\n"
                        "    commands:\n"
                        "      start: docker compose up -d postgres\n"
                        "      ready: pg_isready -h localhost -p 5432\n"
                        "      stop: docker compose stop postgres\n"
                        "\n"
                        "  redis:\n"
                        "    name: Redis Cache\n"
                        "    triggeredBy:\n"
                        "      - postDevcontainerStart\n"
                        "    commands:\n"
                        "      start: docker compose up -d redis\n"
                        "      ready: redis-cli ping\n"
                        "\n"
                        "tasks:\n"
                        "  run-migrations:\n"
                        "    name: Run Migrations\n"
                        "    command: python manage.py migrate\n"
                        "    triggeredBy:\n"
                        "      - postDevcontainerStart\n"
                        "    dependsOn:\n"
                        "      - postgres"
                    ),
                    filename=".ona/automations.yaml",
                ),
            ),
            LessonStep(
                title="Testing & Verification",
                explanation=(
                    "Before pushing, verify your services configuration:\n\n"
                    "**1. Validate Configuration Files**"
                ),
                code=CodeBlock(
                    language="bash",
                    source=(
                        "# Validate YAML syntax\n"
                        "python3 -c 'import yaml; yaml.safe_load(open(\".ona/automations.yaml\"))'\n"
                        "python3 -c 'import yaml; yaml.safe_load(open(\"docker-compose.yaml\"))'\n"
                        "\n"
                        "# Check network_mode: host is set\n"
                        "grep -n 'network_mode: host' docker-compose.yaml"
                    ),
                ),
                deep_dive=(
                    "**2. Test Services Locally**\n\n"
                    "Test Docker Compose services before pushing:\n"
                    "```bash\n"
                    "# Start services locally\n"
                    "docker compose up -d\n"
                    "\n"
                    "# Check services are running\n"
                    "docker ps\n"
                    "\n"
                    "# Test health checks\n"
                    "pg_isready -h localhost -p 5432\n"
                    "redis-cli ping\n"
                    "\n"
                    "# Clean up\n"
                    "docker compose down\n"
                    "```\n\n"
                    "**3. Test in ONA Environment**\n\n"
                    "After pushing and rebuilding:\n"
                    "```bash\n"
                    "# Check service status\n"
                    "gitpod automations service list\n"
                    "gitpod automations service status postgres\n"
                    "\n"
                    "# View service logs\n"
                    "docker compose logs postgres\n"
                    "docker compose logs redis\n"
                    "```\n\n"
                    "**Expected Success Indicators:**\n"
                    "- ✓ Services start automatically after devcontainer starts\n"
                    "- ✓ Health checks pass (ready command returns 0)\n"
                    "- ✓ Services accessible on localhost\n"
                    "- ✓ Dependent tasks wait for services to be ready\n\n"
                    "**Common Issues:**\n"
                    "- Missing `network_mode: host` → Services unreachable\n"
                    "- Health check fails → Check service is actually running\n"
                    "- Port conflicts → Another service using the same port\n"
                    "- Wrong command syntax → Test `commands.start` manually"
                ),
            ),
            LessonStep(
                title="Apply to Your Repository",
                explanation=(
                    "Scaffolding writes files under "
                    "`./cde-onboard-practice/lesson-04-services/`. "
                    "Copy both `.ona/automations.yaml` and `docker-compose.yaml` "
                    "into your repo, then commit."
                ),
                code=CodeBlock(
                    language="bash",
                    source=(
                        "# From your repo root\n"
                        "cp -R cde-onboard-practice/lesson-04-services/.ona ./\n"
                        "cp cde-onboard-practice/lesson-04-services/docker-compose.yaml ./\n"
                        "git add .ona/automations.yaml docker-compose.yaml\n"
                        "git commit -m \"Add service automations\"\n"
                        "git push"
                    ),
                ),
            ),
        ]

    def scaffold_files(self) -> list[ScaffoldFile]:
        return [
            ScaffoldFile(
                relative_path=".ona/automations.yaml",
                template_name="automations_service.yaml",
                description="Service + task automations with PostgreSQL, Redis, and migrations.",
            ),
            ScaffoldFile(
                relative_path="docker-compose.yaml",
                template_name="docker_compose_service.yaml",
                description="Docker Compose file with PostgreSQL and Redis using network_mode: host.",
            ),
        ]
