"""
Runtime loader for releaseops bundles.

Provides a simple SDK for loading bundles at runtime with automatic
telemetry injection. Makes integration a one-liner for agent code.
"""

from __future__ import annotations

import logging
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Dict, Generator, Optional, Tuple

import yaml

from llmhq_releaseops.core.resolver import Resolver
from llmhq_releaseops.models.bundle import BundleManifest
from llmhq_releaseops.storage.git_store import GitStore
from llmhq_releaseops.telemetry.context import bundle_context
from llmhq_releaseops.telemetry.injector import TelemetryInjector
from llmhq_releaseops.telemetry.models import TelemetryContext

logger = logging.getLogger(__name__)


class RuntimeLoader:
    """
    Unified API for loading bundles at runtime with telemetry.

    Usage:
        loader = RuntimeLoader()
        bundle, metadata = loader.load_bundle("agent@prod")
    """

    def __init__(
        self,
        base_dir: Path = None,
        auto_inject_telemetry: bool = True,
    ):
        self._base_dir = Path(base_dir) if base_dir else Path.cwd()
        self._auto_inject_telemetry = auto_inject_telemetry
        self._store = GitStore(str(self._base_dir))
        self._resolver = Resolver(self._store)
        self._injector = TelemetryInjector()
        self._otel_integration = None

    def _get_otel_integration(self):
        """Lazy-load OpenTelemetry integration."""
        if self._otel_integration is None:
            from llmhq_releaseops.telemetry.integrations.opentelemetry import (
                OpenTelemetryIntegration,
            )
            self._otel_integration = OpenTelemetryIntegration()
        return self._otel_integration

    @staticmethod
    def _parse_ref(bundle_ref: str) -> Tuple[str, str]:
        """Parse 'bundle-id@environment' reference."""
        if "@" not in bundle_ref:
            raise ValueError(
                f"Invalid bundle reference '{bundle_ref}'. "
                "Expected format: 'bundle-id@environment' (e.g., 'agent@prod')"
            )
        parts = bundle_ref.split("@", 1)
        return parts[0], parts[1]

    def load_bundle(
        self,
        bundle_ref: str,
        inject_telemetry: Optional[bool] = None,
    ) -> Tuple[BundleManifest, TelemetryContext]:
        """
        Load a bundle by reference and extract telemetry metadata.

        Args:
            bundle_ref: Reference in 'bundle-id@environment' format.
            inject_telemetry: Override auto_inject_telemetry setting.

        Returns:
            Tuple of (BundleManifest, TelemetryContext).
        """
        bundle_id, env = self._parse_ref(bundle_ref)

        bundle = self._resolver.resolve(bundle_id, env)
        metadata = self._injector.extract_metadata(bundle, env)

        should_inject = (
            inject_telemetry if inject_telemetry is not None
            else self._auto_inject_telemetry
        )

        if should_inject:
            try:
                otel = self._get_otel_integration()
                otel.inject_current_span(metadata)
                from llmhq_releaseops.telemetry.context import set_bundle_metadata
                set_bundle_metadata(metadata)
            except Exception as e:
                logger.warning(f"Telemetry injection failed (non-fatal): {e}")

        return bundle, metadata

    def load_bundle_content(self, bundle_ref: str) -> Dict[str, Any]:
        """
        Load bundle with resolved content for easy consumption.

        Returns:
            Dict with keys: bundle, metadata, prompts, policies, model.
        """
        bundle, metadata = self.load_bundle(bundle_ref)

        prompts: Dict[str, str] = {}
        for role, ref in bundle.prompts.items():
            try:
                from llmhq_releaseops.integration.prompt_bridge import PromptBridge
                bridge = PromptBridge(str(self._base_dir))
                prompt_id = ref.ref.split("/")[-1].split("@")[0] if "/" in ref.ref else ref.ref
                version = ref.ref.split("@")[-1] if "@" in ref.ref else None
                prompts[role] = bridge.resolve_prompt(prompt_id, version)
            except Exception as e:
                logger.warning(f"Could not load prompt '{role}': {e}")
                prompts[role] = f"<error loading: {e}>"

        policies: Dict[str, Any] = {}
        for role, ref in bundle.policies.items():
            try:
                policy_path = self._base_dir / ref.ref
                if policy_path.exists():
                    with open(policy_path) as f:
                        policies[role] = yaml.safe_load(f)
                else:
                    policies[role] = f"<file not found: {ref.ref}>"
            except Exception as e:
                logger.warning(f"Could not load policy '{role}': {e}")
                policies[role] = f"<error loading: {e}>"

        model = metadata.model_config

        return {
            "bundle": bundle,
            "metadata": metadata,
            "prompts": prompts,
            "policies": policies,
            "model": model,
        }

    @contextmanager
    def load_bundle_context(
        self, bundle_ref: str
    ) -> Generator[BundleManifest, None, None]:
        """
        Context manager that loads bundle with telemetry, cleans up on exit.

        Usage:
            with loader.load_bundle_context("agent@prod") as bundle:
                agent = create_agent(bundle)
                agent.run()
        """
        bundle, metadata = self.load_bundle(bundle_ref, inject_telemetry=False)
        with bundle_context(metadata):
            try:
                otel = self._get_otel_integration()
                otel.inject_current_span(metadata)
            except Exception as e:
                logger.warning(f"Telemetry injection failed (non-fatal): {e}")
            yield bundle
