"""Manual runner for the management-code step.

Tests management-code sub-steps against an existing generated project
without running the full pipeline.

Usage:
  uv run python pipeline/management-code/run.py --run-id bmi_calculator_application_1778480873055 --execute-gitlab
  uv run python pipeline/management-code/run.py --run-id bmi_calculator_application_1778480873055 --execute-efs --efs-env freemeal
  uv run python pipeline/management-code/run.py --run-id bmi_calculator_application_1778480873055 --execute-jenkins --gitlab-url https://gitlab.example.com/freemeal/bmi_calculator_application_1778480873055
  uv run python pipeline/management-code/run.py --run-id bmi_calculator_application_1778480873055 --execute-runner --gitlab-url https://gitlab.example.com/freemeal/bmi_calculator_application_1778480873055
"""
from __future__ import annotations

import argparse
import sys
from pathlib import Path

# ── Path setup ────────────────────────────────────────────────────────────────
ROOT_DIR      = Path(__file__).resolve().parents[2]
SYNC_PIPELINE = Path(__file__).resolve().parent / "pipeline"

for _p in (str(ROOT_DIR), str(SYNC_PIPELINE)):
    if _p not in sys.path:
        sys.path.insert(0, _p)

try:
    from dotenv import load_dotenv  # type: ignore
    load_dotenv(ROOT_DIR / ".env", override=False)
except ImportError:
    pass

from shared.config import (  # noqa: E402
    EFS_ENV, EFS_MOUNT,
    GITLAB_URL, GITLAB_ADMIN_TOKEN,
    JENKINS_URL, JENKINS_ADMIN_USER, JENKINS_ADMIN_TOKEN,
    JENKINS_USERNAME, JENKINS_USER_PASSWORD, JENKINS_GIT_CREDENTIALS_ID,
    RUNNER_URL, RUNNER_ADMIN_USER, RUNNER_ADMIN_TOKEN, RUNNER_GIT_CREDENTIALS_ID,
    DB_ADMIN_HOST,
)
from shared.logging import get_logger                                           # noqa: E402
from gitlab_push_service import GitLabConfig, push_run_to_gitlab               # noqa: E402
from efs_sync_service import EFSConfig, sync_project_to_efs                    # noqa: E402
from jenkins_pipeline_service import JenkinsConfig, setup_run_in_jenkins, trigger_run_in_jenkins, get_last_build_num, stream_jenkins_build  # noqa: E402
from runner_pipeline_service import RunnerJobTemplate, setup_run_in_runner, trigger_run_in_runner  # noqa: E402
from project_setup_service import setup_project                                # noqa: E402
from project_build_service import build_project                                # noqa: E402
from scripts_service import ScriptsConfig, generate_backend_scripts            # noqa: E402

logger = get_logger(__name__)

_DIVIDER = "─" * 55


def _find_project(run_id: str) -> Path:
    runs_dir = ROOT_DIR / "runs" / "outputs"
    if not runs_dir.exists():
        raise FileNotFoundError(f"No runs directory at {runs_dir}")
    exact = runs_dir / run_id
    if exact.is_dir():
        return exact
    matches = [d for d in runs_dir.iterdir() if d.is_dir() and d.name.endswith(f"_{run_id}")]
    if not matches:
        raise FileNotFoundError(f"No project found for run_id '{run_id}'")
    if len(matches) > 1:
        raise ValueError(f"Multiple matches for '{run_id}': {[d.name for d in matches]}")
    return matches[0]


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(
        description="Run management-code steps against an existing project.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )
    parser.add_argument("--run-id", default=None,
                        help="Project slug or run ID (looked up in runs/outputs/).")
    parser.add_argument("--project-dir", default=None,
                        help="Explicit path to the generated project root.")
    parser.add_argument("--execute-repo-setup", action="store_true", help="Patch EFS .env DB_HOST and generate Dockerfile + k8s manifests into the backend dir.")
    parser.add_argument("--execute-gitlab", action="store_true", help="Run GitLab push.")
    parser.add_argument("--org-code", default=None,
                        help=f"GitLab org code / tenant (overrides EFS_ENV={EFS_ENV!r}).")
    parser.add_argument("--execute-efs", action="store_true", help="Run EFS sync.")
    parser.add_argument("--efs-env", default=None,
                        help=f"EFS environment/tenant name (overrides EFS_ENV={EFS_ENV!r}).")
    parser.add_argument("--execute-setup", action="store_true", help="Run project setup (npm install, uv sync).")
    parser.add_argument("--execute-build", action="store_true", help="Run project build (npm run build).")
    parser.add_argument("--execute-jenkins", action="store_true", help="Run Jenkins pipeline setup.")
    parser.add_argument("--execute-runner", action="store_true", help="Run runner pipeline setup.")
    parser.add_argument("--trigger-jenkins", action="store_true", help="Trigger existing Jenkins pipeline job.")
    parser.add_argument("--stream-jenkins", action="store_true", help="Trigger Jenkins pipeline job and stream console output.")
    parser.add_argument("--trigger-runner", action="store_true", help="Trigger existing runner pipeline job.")
    parser.add_argument("--stream-runner", action="store_true", help="Trigger runner pipeline job and stream console output.")
    parser.add_argument("--job", default="frontend", choices=["frontend", "backend"],
                        help="Which job to trigger (default: frontend).")
    parser.add_argument("--gitlab-url", default=None,
                        help="GitLab group URL for this run (required by --execute-jenkins/--execute-runner).")
    args = parser.parse_args(argv)

    # ── Resolve project dir ───────────────────────────────────────────────────
    try:
        if args.run_id:
            project_root = _find_project(args.run_id)
        elif args.project_dir:
            project_root = Path(args.project_dir).expanduser().resolve()
            if not project_root.exists():
                print(f"ERROR: project dir not found: {project_root}", file=sys.stderr)
                return 1
        else:
            print("ERROR: provide --run-id or --project-dir", file=sys.stderr)
            return 1
    except (FileNotFoundError, ValueError) as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 1

    project_slug = project_root.name

    print(_DIVIDER)
    print("  management-code")
    print(_DIVIDER)
    print(f"  Project : {project_root}")

    rc = 0
    gitlab_url: str | None = args.gitlab_url  # may be pre-supplied or set by gitlab step below

    # ── Project setup ────────────────────────────────────────────────────────
    if args.execute_setup:
        efs_env = args.efs_env or EFS_ENV
        setup_dir = (
            Path(EFS_MOUNT) / efs_env / "workspace" / project_slug
            if efs_env else project_root
        )
        print(f"\n[setup] Installing dependencies in '{setup_dir}'...")
        try:
            setup_project(setup_dir)
            print(f"[setup] Done → {setup_dir}")
        except Exception as exc:
            print(f"[setup] FAILED: {exc}", file=sys.stderr)
            rc = 1

    # ── Repo setup (patch EFS .env + generate manifests) ─────────────────────
    if args.execute_repo_setup:
        scripts_dir = project_root / "backend"
        efs_env = args.efs_env or EFS_ENV
        efs_backend_dir = Path(EFS_MOUNT) / efs_env / "workspace" / project_slug / "backend" if efs_env else None
        env_source = efs_backend_dir if efs_backend_dir and (efs_backend_dir / ".env").exists() else None
        if env_source:
            print(f"\n[repo-setup] EFS .env → {env_source / '.env'}")
        print(f"\n[repo-setup] Setting up backend repo in '{scripts_dir}'...")
        try:
            cfg = ScriptsConfig(db_host=DB_ADMIN_HOST or None)
            generate_backend_scripts(scripts_dir, run_id=project_slug, config=cfg, env_source_dir=env_source)
        except Exception as exc:
            print(f"[repo-setup] FAILED: {exc}", file=sys.stderr)
            rc = 1

    # ── GitLab push ───────────────────────────────────────────────────────────
    if args.execute_gitlab:
        org_code = args.org_code or EFS_ENV
        if not (GITLAB_URL and GITLAB_ADMIN_TOKEN and org_code):
            print("\n[gitlab] Skipped — GITLAB_URL / GITLAB_ADMIN_TOKEN / EFS_ENV (tenant) not set.")
        else:
            gitlab_cfg = GitLabConfig(url=GITLAB_URL, admin_token=GITLAB_ADMIN_TOKEN, org_code=org_code)
            print(f"\n[gitlab] Pushing '{project_slug}' to {GITLAB_URL} / {org_code}...")
            try:
                url = push_run_to_gitlab(
                    config=gitlab_cfg,
                    run_dir=project_root,
                    run_id=project_slug,
                    commit_msg=f"generate: {project_slug}",
                )
                gitlab_url = url
                print(f"[gitlab] Pushed → {url}")
            except Exception as exc:
                print(f"[gitlab] FAILED: {exc}", file=sys.stderr)
                rc = 1

    # ── EFS sync ──────────────────────────────────────────────────────────────
    if args.execute_efs:
        efs_env = args.efs_env or EFS_ENV
        if not efs_env:
            print("\n[efs] Skipped — EFS_ENV not set (use --efs-env or set EFS_ENV in .env).")
        else:
            efs_config = EFSConfig(mount=Path(EFS_MOUNT), env=efs_env, db_host=DB_ADMIN_HOST)
            folders = [f for f in ("frontend", "backend") if (project_root / f).exists()]
            print(f"\n[efs] Syncing {folders} → {efs_config.workspace} (env={efs_env})...")
            try:
                dest = sync_project_to_efs(
                    config=efs_config, project_root=project_root, project_slug=project_slug,
                )
                print(f"[efs] Synced → {dest}")
            except Exception as exc:
                print(f"[efs] FAILED: {exc}", file=sys.stderr)
                rc = 1

    # ── Jenkins pipeline setup ────────────────────────────────────────────────
    if args.execute_jenkins:
        if not (JENKINS_URL and JENKINS_ADMIN_USER and JENKINS_ADMIN_TOKEN and JENKINS_USERNAME):
            print("\n[jenkins] Skipped — JENKINS_URL / JENKINS_ADMIN_USER / JENKINS_ADMIN_TOKEN / JENKINS_USERNAME not set.")
        elif not gitlab_url:
            print("\n[jenkins] Skipped — gitlab URL not available (run --execute-gitlab first or pass --gitlab-url).", file=sys.stderr)
            rc = 1
        else:
            jenkins_cfg = JenkinsConfig(
                url=JENKINS_URL,
                admin_user=JENKINS_ADMIN_USER,
                admin_token=JENKINS_ADMIN_TOKEN,
                username=JENKINS_USERNAME,
                user_password=JENKINS_USER_PASSWORD or None,
                git_credentials_id=JENKINS_GIT_CREDENTIALS_ID or None,
            )
            print(f"\n[jenkins] Setting up '{project_slug}' in {JENKINS_URL}...")
            try:
                folder_url = setup_run_in_jenkins(
                    config=jenkins_cfg,
                    run_id=project_slug,
                    gitlab_url=gitlab_url,
                )
                print(f"[jenkins] Done → {folder_url}")
            except Exception as exc:
                print(f"[jenkins] FAILED: {exc}", file=sys.stderr)
                rc = 1

    # ── Runner pipeline setup ─────────────────────────────────────────────────
    if args.execute_runner:
        if not (RUNNER_URL and RUNNER_ADMIN_USER and RUNNER_ADMIN_TOKEN):
            print("\n[runner] Skipped — RUNNER_URL / RUNNER_ADMIN_USER / RUNNER_ADMIN_TOKEN not set.")
        elif not gitlab_url:
            print("\n[runner] Skipped — gitlab URL not available (run --execute-gitlab first or pass --gitlab-url).", file=sys.stderr)
            rc = 1
        else:
            runner_cfg = JenkinsConfig(
                url=RUNNER_URL,
                admin_user=RUNNER_ADMIN_USER,
                admin_token=RUNNER_ADMIN_TOKEN,
                username=RUNNER_ADMIN_USER,
                git_credentials_id=RUNNER_GIT_CREDENTIALS_ID or None,
            )
            print(f"\n[runner] Setting up '{project_slug}' in {RUNNER_URL}...")
            try:
                folder_url = setup_run_in_runner(
                    config=runner_cfg,
                    run_id=project_slug,
                    gitlab_url=gitlab_url,
                    template=RunnerJobTemplate(),
                )
                print(f"[runner] Done → {folder_url}")
            except Exception as exc:
                print(f"[runner] FAILED: {exc}", file=sys.stderr)
                rc = 1

    # ── Trigger Jenkins job ───────────────────────────────────────────────────
    if args.trigger_jenkins:
        if not (JENKINS_URL and JENKINS_ADMIN_USER and JENKINS_ADMIN_TOKEN and JENKINS_USERNAME):
            print("\n[jenkins] Skipped — JENKINS_URL / JENKINS_ADMIN_USER / JENKINS_ADMIN_TOKEN / JENKINS_USERNAME not set.")
        else:
            jenkins_cfg = JenkinsConfig(
                url=JENKINS_URL, admin_user=JENKINS_ADMIN_USER,
                admin_token=JENKINS_ADMIN_TOKEN, username=JENKINS_USERNAME,
                user_password=JENKINS_USER_PASSWORD or None,
                git_credentials_id=JENKINS_GIT_CREDENTIALS_ID or None,
            )
            job = args.job
            print(f"\n[jenkins] Triggering '{job}' for '{project_slug}' in {JENKINS_URL}...")
            try:
                job_url = trigger_run_in_jenkins(jenkins_cfg, run_id=project_slug, job=job)
                print(f"[jenkins] Triggered → {job_url}")
            except Exception as exc:
                print(f"[jenkins] FAILED: {exc}", file=sys.stderr)
                rc = 1

    # ── Stream Jenkins job (trigger + tail console) ───────────────────────────
    if args.stream_jenkins:
        if not (JENKINS_URL and JENKINS_ADMIN_USER and JENKINS_ADMIN_TOKEN and JENKINS_USERNAME):
            print("\n[jenkins] Skipped — JENKINS_URL / JENKINS_ADMIN_USER / JENKINS_ADMIN_TOKEN / JENKINS_USERNAME not set.")
        else:
            jenkins_cfg = JenkinsConfig(
                url=JENKINS_URL, admin_user=JENKINS_ADMIN_USER,
                admin_token=JENKINS_ADMIN_TOKEN, username=JENKINS_USERNAME,
                user_password=JENKINS_USER_PASSWORD or None,
                git_credentials_id=JENKINS_GIT_CREDENTIALS_ID or None,
            )
            job = args.job
            print(f"\n[jenkins] Triggering '{job}' for '{project_slug}' in {JENKINS_URL}...")
            try:
                prev_num = get_last_build_num(jenkins_cfg, folder=project_slug, job=job)
                job_url = trigger_run_in_jenkins(jenkins_cfg, run_id=project_slug, job=job)
                print(f"[jenkins] Triggered → {job_url}", flush=True)
                success = stream_jenkins_build(
                    jenkins_cfg, folder=project_slug, job=job,
                    prev_build_num=prev_num, label="jenkins",
                )
                if not success:
                    print(f"[jenkins] FAILED", file=sys.stderr)
                    rc = 1
            except Exception as exc:
                print(f"[jenkins] FAILED: {exc}", file=sys.stderr)
                rc = 1

    # ── Trigger Runner job ────────────────────────────────────────────────────
    if args.trigger_runner:
        if not (RUNNER_URL and RUNNER_ADMIN_USER and RUNNER_ADMIN_TOKEN):
            print("\n[runner] Skipped — RUNNER_URL / RUNNER_ADMIN_USER / RUNNER_ADMIN_TOKEN not set.")
        else:
            runner_cfg = JenkinsConfig(
                url=RUNNER_URL, admin_user=RUNNER_ADMIN_USER,
                admin_token=RUNNER_ADMIN_TOKEN, username=RUNNER_ADMIN_USER,
                git_credentials_id=RUNNER_GIT_CREDENTIALS_ID or None,
            )
            job = args.job
            print(f"\n[runner] Triggering '{job}' for '{project_slug}' in {RUNNER_URL}...")
            try:
                job_url = trigger_run_in_runner(runner_cfg, run_id=project_slug, job=job)
                print(f"[runner] Triggered → {job_url}")
            except Exception as exc:
                print(f"[runner] FAILED: {exc}", file=sys.stderr)
                rc = 1

    # ── Stream Runner job (trigger + tail console) ────────────────────────────
    if args.stream_runner:
        if not (RUNNER_URL and RUNNER_ADMIN_USER and RUNNER_ADMIN_TOKEN):
            print("\n[runner] Skipped — RUNNER_URL / RUNNER_ADMIN_USER / RUNNER_ADMIN_TOKEN not set.")
        else:
            runner_cfg = JenkinsConfig(
                url=RUNNER_URL, admin_user=RUNNER_ADMIN_USER,
                admin_token=RUNNER_ADMIN_TOKEN, username=RUNNER_ADMIN_USER,
                git_credentials_id=RUNNER_GIT_CREDENTIALS_ID or None,
            )
            job = args.job
            print(f"\n[runner] Triggering '{job}' for '{project_slug}' in {RUNNER_URL}...")
            try:
                prev_num = get_last_build_num(runner_cfg, folder=project_slug, job=job)
                job_url = trigger_run_in_runner(runner_cfg, run_id=project_slug, job=job)
                print(f"[runner] Triggered → {job_url}", flush=True)
                success = stream_jenkins_build(
                    runner_cfg, folder=project_slug, job=job,
                    prev_build_num=prev_num, label="runner",
                )
                if not success:
                    print(f"[runner] FAILED", file=sys.stderr)
                    rc = 1
            except Exception as exc:
                print(f"[runner] FAILED: {exc}", file=sys.stderr)
                rc = 1

    # ── Project build ─────────────────────────────────────────────────────────
    if args.execute_build:
        efs_env = args.efs_env or EFS_ENV
        build_dir = (
            Path(EFS_MOUNT) / efs_env / "workspace" / project_slug
            if efs_env else project_root
        )
        print(f"\n[build] Building project in '{build_dir}'...")
        try:
            build_project(build_dir)
            print(f"[build] Done → {build_dir}")
        except Exception as exc:
            print(f"[build] FAILED: {exc}", file=sys.stderr)
            rc = 1

    if not any([args.execute_setup, args.execute_repo_setup, args.execute_gitlab, args.execute_efs,
                args.execute_jenkins, args.execute_runner, args.execute_build]):
        print("\nNothing to do — pass --execute-gitlab, --execute-efs, --execute-jenkins, and/or --execute-runner.")

    print(_DIVIDER)
    print("  Done." if rc == 0 else "  Finished with errors.")
    print(_DIVIDER)
    return rc


if __name__ == "__main__":
    sys.exit(main())
