"""Deployment target kind resolution, build configuration, and workflow generation."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

# Explicit set of target kinds the system understands.
DeploymentTargetKind = Literal[
    "cloudflare_pages",   # CF Pages via GitHub Actions direct upload — html, react_app, nuxt_app
    "cloudflare_worker",  # CF Workers Script via GitHub Actions wrangler deploy — shopify_app
    "external_origin",    # External host — wordpress_theme, shopify_theme, etc.
]

# Routing is always W4P KV + proxy Workers.
ROUTING_PROVIDER_KIND = "cloudflare_w4p"


@dataclass(frozen=True)
class PagesBuildConfig:
    build_command: str
    destination_dir: str  # empty string → "." (serve repo root, e.g. html projects)
    root_dir: str = ""


# Per-framework CF Pages build configuration.
_BUILD_CONFIGS: dict[str, PagesBuildConfig] = {
    # html: no build step; serve root directory.
    "html": PagesBuildConfig(build_command="", destination_dir=""),
    # react_app: Vite produces dist/
    "react_app": PagesBuildConfig(build_command="npm run build", destination_dir="dist"),
    # W4P uploads static assets and requires a root index.html. The Nuxt W4P
    # workflow invokes `nuxt generate` explicitly because the legacy project
    # script named `generate` uses `nuxt build` and emits no SPA shell.
    "nuxt_app": PagesBuildConfig(build_command="npm run generate", destination_dir=".output/public"),
}

_CLOUDFLARE_PAGES_TYPES = frozenset({"html", "react_app", "nuxt_app"})
_CLOUDFLARE_WORKER_TYPES = frozenset({"shopify_app"})
_EXTERNAL_ORIGIN_TYPES = frozenset({"wordpress_theme", "shopify_theme"})


def decide_target_kind(project_type: str) -> DeploymentTargetKind:
    if project_type in _CLOUDFLARE_PAGES_TYPES:
        return "cloudflare_pages"
    if project_type in _CLOUDFLARE_WORKER_TYPES:
        return "cloudflare_worker"
    return "external_origin"


def get_pages_build_config(project_type: str) -> PagesBuildConfig:
    try:
        return _BUILD_CONFIGS[project_type]
    except KeyError as exc:
        raise ValueError(f"unsupported_project_type:{project_type}") from exc


def pages_project_name(project_ref: str) -> str:
    """Deterministic CF Pages project name from project_ref.

    Format: ``p2hs-{project_ref}`` — lowercase, alphanumeric + hyphens, ≤ 50 chars.
    project_ref is always 9 chars (p + 8 hex), so total is 14 chars.
    """
    slug = "".join(c if c.isalnum() or c == "-" else "-" for c in project_ref.lower())
    return f"p2hs-{slug}"[:50]


def pages_production_url(project_ref: str) -> str:
    """Stable CF Pages production URL (known before first deployment)."""
    return f"https://{pages_project_name(project_ref)}.pages.dev"


def pages_preview_branch_url(project_ref: str, preview_branch: str = "preview") -> str:
    """Stable CF Pages branch preview URL (known before first deployment)."""
    safe_branch = preview_branch.lower().replace("/", "-").replace("_", "-")
    return f"https://{safe_branch}.{pages_project_name(project_ref)}.pages.dev"


def worker_script_name(project_ref: str) -> str:
    """Deterministic CF Worker script name from project_ref.

    Format: ``p2hs-shopify-{project_ref}`` — max 63 chars (CF Workers limit).
    """
    slug = "".join(c if c.isalnum() or c == "-" else "-" for c in project_ref.lower())
    return f"p2hs-shopify-{slug}"[:63]


def generate_worker_deploy_workflow(project_ref: str) -> str:
    """Return the GitHub Actions deploy.yml for a CF Worker (shopify_app) project.

    Triggers on push to ``main`` only (no preview branch for Workers).
    Uses ``wrangler deploy`` — not ``wrangler pages deploy``.

    Secrets required on the GitHub org:
      CLOUDFLARE_API_TOKEN  — CF API token with Workers:Edit permission
      CLOUDFLARE_ACCOUNT_ID — CF account ID
    """
    script_name = worker_script_name(project_ref)
    steps = [
        "      - uses: actions/checkout@v4",
        "      - uses: actions/setup-node@v4",
        "        with:",
        "          node-version: '22'",
        "      - name: Install locked app dependencies",
        "        run: test -f package-lock.json && npm ci",
        "      - name: Deploy to Cloudflare Workers",
        "        uses: cloudflare/wrangler-action@v3",
        "        with:",
        "          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}",
        "          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}",
        f"          command: deploy --name={script_name}",
    ]

    return (
        "name: Deploy to Cloudflare Workers\n"
        "on:\n"
        "  push:\n"
        "    branches: [main]\n"
        "  workflow_dispatch: {}\n"
        "\n"
        "jobs:\n"
        "  deploy:\n"
        "    runs-on: ubuntu-latest\n"
        "    permissions:\n"
        "      contents: read\n"
        "    env:\n"
        "      FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'\n"
        "    steps:\n"
        + "\n".join(steps)
        + "\n"
    )


def generate_w4p_deploy_workflow(
    project_type: str,
    project_ref: str,
    *,
    w4p_namespace: str,
    preview_namespace: str | None = None,
    compatibility_date: str,
    core_api_url: str,
    activation_audience: str,
) -> str:
    """deploy.yml for a W4P static project: build, upload to W4P, then activate through Core.

    No Pages project is created or used. The run requests a GitHub OIDC token, which Core verifies
    before it moves the route.
    """
    from src.domain.hosting.w4p_workflow import w4p_deploy_steps

    if not core_api_url or not activation_audience:
        raise ValueError("w4p_deploy_workflow_requires_core_api_url_and_activation_audience")
    build_cfg = get_pages_build_config(project_type)
    output_dir = build_cfg.destination_dir or "."
    build_steps_lines = []
    if build_cfg.build_command:
        build_command = build_cfg.build_command
        if project_type == "nuxt_app":
            build_command = "NITRO_PRESET=cloudflare_pages npx nuxt generate"
        build_steps_lines = [
            "      - uses: actions/setup-node@v4",
            "        with:",
            "          node-version: '24'",
            "      - name: Install locked app dependencies",
            "        run: test -f package-lock.json && npm ci",
            f"      - run: {build_command}",
        ]
        if project_type == "nuxt_app":
            build_steps_lines.extend([
                "      - name: Prepare W4P static assets",
                "        run: |",
                "          if [ -d .output/public ]; then",
                "            W4P_STATIC_DIR=.output/public",
                "          elif [ -d dist ]; then",
                "            W4P_STATIC_DIR=dist",
                "          else",
                "            echo 'Nuxt generation produced no .output/public or dist directory' >&2",
                "            find . -maxdepth 3 -type d -name public -o -name dist -o -name .output >&2 || true",
                "            exit 1",
                "          fi",
                "          if [ ! -f \"$W4P_STATIC_DIR/index.html\" ] && [ -f \"$W4P_STATIC_DIR/200.html\" ]; then",
                "            cp \"$W4P_STATIC_DIR/200.html\" \"$W4P_STATIC_DIR/index.html\"",
                "          fi",
                "          if [ ! -f \"$W4P_STATIC_DIR/index.html\" ]; then",
                "            echo \"W4P static directory has no index.html: $W4P_STATIC_DIR\" >&2",
                "            exit 1",
                "          fi",
                "          echo \"W4P_STATIC_DIR=$W4P_STATIC_DIR\" >> \"$GITHUB_ENV\"",
            ])
    steps = ["      - uses: actions/checkout@v4"] + build_steps_lines + [
        w4p_deploy_steps(
            project_ref=project_ref,
            static_dir=output_dir,
            w4p_namespace=w4p_namespace,
            preview_namespace=preview_namespace,
            compatibility_date=compatibility_date,
            core_api_url=core_api_url,
            activation_audience=activation_audience,
        ).rstrip("\n")
    ]
    return (
        "name: Deploy to Cloudflare W4P\n"
        "on:\n"
        "  push:\n"
        "    branches: [main, preview]\n"
        "  workflow_dispatch: {}\n"
        "\n"
        "jobs:\n"
        "  deploy:\n"
        "    runs-on: ubuntu-latest\n"
        "    permissions:\n"
        "      contents: read\n"
        "      id-token: write\n"
        "    env:\n"
        "      FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'\n"
        "    steps:\n"
        + "\n".join(steps)
        + "\n"
    )


def is_w4p_project(project_type: str, project_created_at, settings, *, primary_hosting_provider=None, assume_unassigned_w4p: bool = False, has_successful_pages_deployment: bool | None = None) -> bool:
    """True when this project deploys through W4P static assets instead of a Pages project."""
    if settings is None or decide_target_kind(project_type) != "cloudflare_pages":
        return False
    from src.domain.hosting.project_provider import W4P_STATIC, project_hosting_provider

    return project_hosting_provider(
        project_created_at,
        settings,
        primary_hosting_provider=primary_hosting_provider,
        assume_unassigned_w4p=assume_unassigned_w4p,
        has_successful_pages_deployment=has_successful_pages_deployment,
    ) == W4P_STATIC


def deploy_workflow_for_project(project_type: str, project_ref: str, project_created_at, settings, *, primary_hosting_provider=None, assume_unassigned_w4p: bool = False, has_successful_pages_deployment: bool | None = None) -> str:
    """deploy.yml for one project, taken from its resolved Hosting Profile.

    Static projects with no registered profile keep the Pages workflow. An
    explicit unknown provider fails closed instead of being rewritten as Pages.
    Without settings the Pages workflow is returned.
    """
    if settings is None or decide_target_kind(project_type) != "cloudflare_pages":
        return generate_deploy_workflow(project_type, project_ref)
    from src.domain.hosting.profile import (
        HostingProfileError,
        find_hosting_profile,
        render_deploy_workflow,
    )
    from src.domain.hosting.project_provider import project_hosting_provider

    selected = project_hosting_provider(
        project_created_at,
        settings,
        primary_hosting_provider=primary_hosting_provider,
        assume_unassigned_w4p=assume_unassigned_w4p,
        has_successful_pages_deployment=has_successful_pages_deployment,
    )
    profile = find_hosting_profile(selected)
    if profile is None:
        explicit = (primary_hosting_provider or "").strip().lower()
        if explicit and explicit == selected:
            raise HostingProfileError(f"unregistered hosting provider {selected}")
        return generate_deploy_workflow(project_type, project_ref)
    return render_deploy_workflow(profile, project_type, project_ref, settings)


def generate_deploy_workflow(project_type: str, project_ref: str) -> str:
    """Return the GitHub Actions deploy.yml content for this project.

    The workflow:
    - Triggers on push to ``main`` or ``preview`` branches
    - Builds the project (framework-specific)
    - Uploads built output to CF Pages via ``wrangler pages deploy`` (direct upload, no OAuth)
    - Passes ``--commit-hash`` so the backend's CF Pages polling can match deployments by SHA

    Secrets required on the GitHub org (set once by the P2HS operator):
      CLOUDFLARE_API_TOKEN  — CF API token with Pages:Edit permission
      CLOUDFLARE_ACCOUNT_ID — CF account ID
    """
    build_cfg = get_pages_build_config(project_type)
    pages_name = pages_project_name(project_ref)
    output_dir = build_cfg.destination_dir or "."

    if build_cfg.build_command:
        build_steps_lines = [
            "      - uses: actions/setup-node@v4",
            "        with:",
            "          node-version: '24'",
            "      - name: Install locked app dependencies",
            "        run: test -f package-lock.json && npm ci",
            f"      - run: {build_cfg.build_command}",
        ]
    else:
        build_steps_lines = []

    steps = ["      - uses: actions/checkout@v4"] + build_steps_lines + [
        "      - name: Deploy to CF Pages",
        "        uses: cloudflare/wrangler-action@v3",
        "        with:",
        "          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}",
        "          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}",
        "          command: >-",
        f"            pages deploy {output_dir}",
        f"            --project-name={pages_name}",
        "            --branch=${{ github.ref_name }}",
        "            --commit-hash=${{ github.sha }}",
        "            --commit-dirty=true",
    ]

    return (
        "name: Deploy to Cloudflare Pages\n"
        "on:\n"
        "  push:\n"
        "    branches: [main, preview]\n"
        "  workflow_dispatch: {}\n"
        "\n"
        "jobs:\n"
        "  deploy:\n"
        "    runs-on: ubuntu-latest\n"
        "    permissions:\n"
        "      contents: read\n"
        "    env:\n"
        "      FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'\n"
        "    steps:\n"
        + "\n".join(steps)
        + "\n"
    )


def _render_pages_workflow(profile, project_type: str, project_ref: str, settings) -> str:
    del profile, settings
    return generate_deploy_workflow(project_type, project_ref)


def _render_w4p_workflow(profile, project_type: str, project_ref: str, settings) -> str:
    del profile
    return generate_w4p_deploy_workflow(
        project_type,
        project_ref,
        w4p_namespace=(settings.cloudflare_w4p_namespace_production or "p2hs-production").strip(),
        preview_namespace=(
            settings.cloudflare_w4p_namespace_preview
            or settings.cloudflare_w4p_namespace_production
            or "p2hs-preview"
        ).strip(),
        compatibility_date=settings.cloudflare_worker_compatibility_date,
        core_api_url=settings.api_url,
        activation_audience=settings.w4p_activation_audience,
    )


def _register_builtin_workflow_renderers() -> None:
    from src.domain.hosting.profile import register_workflow_renderer

    register_workflow_renderer("cloudflare_pages", _render_pages_workflow)
    register_workflow_renderer("cloudflare_w4p_static", _render_w4p_workflow)


_register_builtin_workflow_renderers()
