"""End-to-end deployment of a scaffolded demo project.

Runs the same deploy scripts that live in the scaffolded project:
  1. ``scripts/deploy-lakehouse.sh`` — bundle deploy + data gen + pipeline + ML + Genie
  2. ``scripts/deploy-app.sh`` — app build + deploy + SP permissions

This ensures the deployer does exactly what the end user would do.
No drift between ``exokit init`` and manual redeployment.
"""

from __future__ import annotations

import logging
import subprocess
from dataclasses import dataclass, field
from pathlib import Path

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Result model
# ---------------------------------------------------------------------------


@dataclass
class ResourceLink:
    """A link to a deployed resource."""

    name: str
    kind: str  # job, pipeline, dashboard, app, genie
    url: str
    status: str = "unknown"


@dataclass
class DeployResult:
    """Outcome of the full deployment pipeline."""

    success: bool = True
    resources: list[ResourceLink] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)

    def add(self, name: str, kind: str, url: str, status: str = "ok") -> None:
        self.resources.append(ResourceLink(name=name, kind=kind, url=url, status=status))


# ---------------------------------------------------------------------------
# Subprocess helpers
# ---------------------------------------------------------------------------

_SCRIPT_TIMEOUT = 600  # 10 minutes for each deploy script


def _run_script(
    script: Path,
    args: list[str] | None = None,
    *,
    cwd: Path,
    timeout: int = _SCRIPT_TIMEOUT,
) -> subprocess.CompletedProcess[str]:
    """Run a shell/python script from the scaffolded project."""
    prefix = "python3" if script.suffix == ".py" else "bash"
    cmd = [prefix, str(script)]
    if args:
        cmd.extend(args)
    return subprocess.run(  # noqa: S603 — args are script paths, not user input
        cmd,
        cwd=cwd,
        capture_output=True,
        text=True,
        timeout=timeout,
    )


# ---------------------------------------------------------------------------
# URL extraction from script output
# ---------------------------------------------------------------------------


def _extract_urls(output: str) -> dict[str, str]:
    """Extract resource URLs from deploy script output.

    Scans for lines containing known URL patterns and categorises them.
    """
    urls: dict[str, str] = {}
    for line in output.splitlines():
        stripped = line.strip()
        if "https://" not in stripped:
            continue
        for token in stripped.split():
            if not token.startswith("https://"):
                continue
            token = token.rstrip(".")
            if "/jobs/" in token or "#job/" in token:
                urls.setdefault("job", token)
            elif "/pipelines/" in token:
                urls.setdefault("pipeline", token)
            elif "/apps/" in token:
                urls.setdefault("app", token)
            elif "/genie/rooms/" in token:
                urls.setdefault("genie", token)
            elif "/dashboards" in token:
                urls.setdefault("dashboard", token)
    return urls


# ---------------------------------------------------------------------------
# Full deployment pipeline
# ---------------------------------------------------------------------------


def deploy_hello_world(
    project_dir: Path,
    *,
    bundle_name: str,
    app_name: str,
    catalog: str,
    warehouse_id: str,
    workspace_host: str,
    target: str = "dev",
    profile: str | None = None,
    on_status: object | None = None,
) -> DeployResult:
    """Run the full deployment pipeline using the scaffolded deploy scripts.

    Calls the same scripts the user would run manually:
        1. ``scripts/deploy-lakehouse.sh`` — bundle deploy, data gen, pipeline,
           ML training, Genie Space
        2. ``scripts/deploy-app.sh`` — app build, deploy, SP permissions

    The ``on_status`` callback (if provided) is called with
    (step_number, message) for progress reporting.
    """
    result = DeployResult()
    host = workspace_host.rstrip("/")

    def status(step: int, msg: str) -> None:
        if callable(on_status):
            on_status(step, msg)

    # ----- Step 1: Lakehouse (bundle deploy + data gen + pipeline + ML + Genie) -----
    status(1, "Running deploy-lakehouse.sh (bundle + data gen + pipeline + ML + Genie)...")
    lakehouse_script = project_dir / "scripts" / "deploy-lakehouse.sh"
    if not lakehouse_script.exists():
        result.success = False
        result.warnings.append("scripts/deploy-lakehouse.sh not found")
        return result

    lakehouse_result = _run_script(
        lakehouse_script, [target], cwd=project_dir, timeout=_SCRIPT_TIMEOUT
    )
    lakehouse_output = lakehouse_result.stdout + "\n" + lakehouse_result.stderr

    if lakehouse_result.returncode != 0:
        result.success = False
        # Parse what succeeded before the failure
        if "Bundle deployed" in lakehouse_output:
            result.add("Bundle Deploy", "bundle", host, "success")
        if "Data generation complete" in lakehouse_output:
            result.add("Data Generation", "job", host, "success")
        if "DLT pipeline complete" in lakehouse_output:
            result.add("DLT Pipeline", "pipeline", host, "success")
        if "ML training complete" in lakehouse_output:
            result.add("ML Training", "job", host, "success")

        # Find the step that failed
        err = lakehouse_result.stderr.strip()[-500:]
        result.warnings.append(f"deploy-lakehouse.sh failed: {err[:300]}")
    else:
        # All lakehouse steps succeeded — add resources
        urls = _extract_urls(lakehouse_output)
        result.add("Data Generation", "job", urls.get("job", host), "success")
        result.add("DLT Pipeline", "pipeline", urls.get("pipeline", host), "success")
        result.add("ML Training", "job", urls.get("job", host), "success")
        result.add("Dashboard", "dashboard", f"{host}/dashboardsv3", "deployed")

        if "genie/rooms/" in lakehouse_output or urls.get("genie"):
            result.add("Genie Space", "genie", urls.get("genie", ""), "created")
        elif "Genie Space creation skipped" in lakehouse_output:
            result.warnings.append("Genie Space creation skipped")
            result.add("Genie Space", "genie", "", "skipped")

    # ----- Step 2: App (build + deploy + SP permissions) -----
    status(2, "Running deploy-app.sh (build + deploy + permissions)...")
    app_script = project_dir / "scripts" / "deploy-app.sh"
    if not app_script.exists() or not app_name:
        result.warnings.append("App deployment skipped (no script or app name)")
        return result

    app_result = _run_script(app_script, [target], cwd=project_dir, timeout=_SCRIPT_TIMEOUT)
    app_output = app_result.stdout + "\n" + app_result.stderr

    if app_result.returncode != 0:
        err = app_result.stderr.strip()[-500:]
        result.warnings.append(f"deploy-app.sh failed: {err[:300]}")
        result.add("App", "app", f"{host}/apps/{app_name}", "failed")
    else:
        app_urls = _extract_urls(app_output)
        result.add("App", "app", app_urls.get("app", f"{host}/apps/{app_name}"), "deployed")
        if "App deployment complete" in app_output:
            logger.info("App deployment completed with SP permissions")

    return result
