"""Convenience helpers for accessing parent jobs and their files."""

from __future__ import annotations

from pathlib import Path
from typing import Any, Iterator

import signac

from .spec import ACTION_SP_KEY, DEFAULT_PARENT_SP_KEY


class DependencyResolutionError(Exception):
    """Raised when a job's parent cannot be resolved."""


def write_dependency_metadata(job: signac.Job, parent_job: signac.Job) -> None:
    """Update ``job.doc['deps_meta']`` with parent job info."""
    deps_meta = dict(job.doc.get("deps_meta", {}))
    deps_meta[parent_job.sp.get(ACTION_SP_KEY, parent_job.id)] = {
        "job_id": parent_job.id,
        "statepoint": dict(parent_job.sp),
    }
    job.doc["deps_meta"] = deps_meta


def get_parent(job: signac.Job, sp_key: str = DEFAULT_PARENT_SP_KEY) -> signac.Job:
    """Return the parent job referenced by *sp_key* in the state point.

    Parameters
    ----------
    job
        The child job whose parent should be resolved.
    sp_key
        State-point key that holds the parent job ID.  Defaults to
        ``DEFAULT_PARENT_SP_KEY`` (``"parent_action"``); pass
        ``action.dep_sp_key`` when using a custom dependency key.

    Raises
    ------
    DependencyResolutionError
        If the job does not declare a parent or the referenced job cannot be opened.
    """

    if sp_key not in job.sp:
        raise DependencyResolutionError(f"Job {job.id} has no '{sp_key}' state point key")
    parent_id = job.sp[sp_key]
    project = job.project
    try:
        return project.open_job(id=str(parent_id))
    except LookupError as exc:
        raise DependencyResolutionError(
            f"Parent job '{parent_id}' not found for job {job.id}"
        ) from exc


def open_parent_folder(job: signac.Job, path: str | Path | None = None) -> Path:
    """Return the filesystem path to the parent job workspace, optionally joined with ``path``."""

    parent = get_parent(job)
    base = Path(parent.path)
    return base / Path(path) if path is not None else base


def parent_file(job: signac.Job, relpath: str | Path, must_exist: bool = True) -> Path:
    """Return an absolute path to a file in the parent workspace."""

    path = open_parent_folder(job, relpath)
    if must_exist and not path.exists():
        raise FileNotFoundError(path)
    return path


def parent_product_exists(job: signac.Job, relpath: str | Path) -> bool:
    """Return True if a given file exists in the parent workspace."""

    return parent_file(job, relpath, must_exist=False).exists()


def parent_path(job: signac.Job) -> Path:
    """Return the parent workspace path for a job."""

    parent = get_parent(job)
    return Path(parent.path)


def iter_parent_products(job: signac.Job, pattern: str = "*") -> Iterator[Path]:
    """Yield paths matching a glob pattern inside the parent workspace."""

    parent = get_parent(job)
    return Path(parent.path).glob(pattern)


def open_job_from_directory(directory: str) -> signac.Job:
    """Open a job by workspace directory name (as passed by row)."""
    dir_path = Path(directory)
    try:
        project = signac.Project.get_project(path=dir_path, search=True)
    except LookupError:
        project = signac.get_project()
    return project.open_job(id=dir_path.name)


def get_parent_doc(job: signac.Job, key: str, default: Any = None) -> Any:
    """Return a value from the parent job document, ignoring reserved keys."""

    parent = get_parent(job)
    if key in parent.doc:
        return parent.doc[key]
    return default
