"""
Wrapped lakeFS client that automatically captures commit SHAs.
"""

from typing import Optional, Dict, Any, Tuple
from datetime import datetime
import logging
import os

try:
    from opentelemetry import trace
    HAS_OTEL = True
except ImportError:
    HAS_OTEL = False

from briefcase.semantic_conventions import lakefs as lakefs_attrs

logger = logging.getLogger(__name__)


class VersionedClient:
    """
    Wrapper around lakeFS client that automatically captures version metadata.

    Configuration priority (highest to lowest):
        1. Explicit parameters
        2. Environment variables (LAKEFS_ENDPOINT, LAKEFS_ACCESS_KEY, LAKEFS_SECRET_KEY)
        3. Default endpoint (https://briefcasebrain.us-east-1.lakefscloud.io/api/v1)

    Usage:
        # Using explicit parameters
        client = VersionedClient(
            repository="acme-healthcare",
            branch="main",
            briefcase_client=briefcase_client,
            lakefs_endpoint="https://lakefs.example.com/api/v1",
            lakefs_access_key="key",
            lakefs_secret_key="secret"
        )

        # Using environment variables
        # export LAKEFS_ENDPOINT="https://lakefs.example.com/api/v1"
        # export LAKEFS_ACCESS_KEY="key"
        # export LAKEFS_SECRET_KEY="secret"
        client = VersionedClient(
            repository="acme-healthcare",
            branch="main",
            briefcase_client=briefcase_client
        )

        # All operations automatically tracked
        content = client.read_object("policies/policy.pdf")
    """

    def __init__(
        self,
        repository: str,
        branch: str,
        commit: str = "latest",
        briefcase_client=None,
        lakefs_endpoint: Optional[str] = None,
        lakefs_access_key: Optional[str] = None,
        lakefs_secret_key: Optional[str] = None
    ):
        self.repository = repository
        self.branch = branch
        self.commit = commit
        self.briefcase_client = briefcase_client

        # Resolve configuration with priority: param > env var > default
        resolved_endpoint = (
            lakefs_endpoint or
            os.getenv("LAKEFS_ENDPOINT") or
            "https://briefcasebrain.us-east-1.lakefscloud.io/api/v1"
        )
        resolved_access_key = lakefs_access_key or os.getenv("LAKEFS_ACCESS_KEY")
        resolved_secret_key = lakefs_secret_key or os.getenv("LAKEFS_SECRET_KEY")

        # Initialize underlying lakeFS client (mock for now, real implementation would use lakefs SDK)
        try:
            import lakefs
            self._lakefs_client = lakefs.Client(
                endpoint=resolved_endpoint,
                access_key=resolved_access_key,
                secret_key=resolved_secret_key
            )
            self._has_lakefs = True
        except (ImportError, Exception) as e:
            logger.warning(f"lakeFS client not available: {e}. Using mock mode.")
            self._lakefs_client = None
            self._has_lakefs = False

        # Resolve commit SHA if "latest" requested
        if self.commit == "latest":
            self.commit = self._resolve_latest_commit()

        # Cache commit metadata
        self._commit_metadata = self._fetch_commit_metadata()

    def _resolve_latest_commit(self) -> str:
        """Resolve 'latest' to actual commit SHA."""
        if not self._has_lakefs or not self._lakefs_client:
            # Mock mode: return fake SHA
            return "abc123def456789012345678901234567890abcd"

        try:
            branch_info = self._lakefs_client.get_branch(
                repository=self.repository,
                branch=self.branch
            )
            return branch_info.commit_id
        except Exception as e:
            logger.error(f"Failed to resolve latest commit: {e}")
            # Return mock SHA on error
            return "abc123def456789012345678901234567890abcd"

    def _fetch_commit_metadata(self) -> Dict[str, Any]:
        """Fetch and cache commit metadata."""
        if not self._has_lakefs or not self._lakefs_client:
            # Mock mode: return fake metadata
            return {
                "sha": self.commit,
                "message": "Mock commit message",
                "author": "mock-author",
                "timestamp": datetime.now().isoformat(),
                "metadata": {}
            }

        try:
            commit = self._lakefs_client.get_commit(
                repository=self.repository,
                commit_id=self.commit
            )
            return {
                "sha": commit.id,
                "message": commit.message,
                "author": commit.committer,
                "timestamp": commit.creation_date,
                "metadata": commit.metadata or {}
            }
        except Exception as e:
            logger.error(f"Failed to fetch commit metadata: {e}")
            # Return mock metadata on error
            return {
                "sha": self.commit,
                "message": "Error fetching commit",
                "author": "unknown",
                "timestamp": datetime.now().isoformat(),
                "metadata": {}
            }

    def read_object(
        self,
        path: str,
        return_metadata: bool = False
    ) -> bytes | Tuple[bytes, Dict]:
        """
        Read an object from lakeFS with automatic instrumentation.

        Args:
            path: Object path (e.g., "policies/policy.pdf")
            return_metadata: If True, return (content, metadata) tuple

        Returns:
            Object content as bytes, optionally with metadata dict
        """
        start_time = datetime.now()

        if not self._has_lakefs or not self._lakefs_client:
            # Mock mode: return fake content
            content = b"Mock file content for " + path.encode()
            etag = "mock-etag-12345"
            last_modified = None
        else:
            try:
                # Read from lakeFS
                obj = self._lakefs_client.get_object(
                    repository=self.repository,
                    ref=self.commit,
                    path=path
                )
                content = obj.read()
                etag = obj.etag
                last_modified = obj.last_modified
            except Exception as e:
                logger.error(f"Failed to read object {path}: {e}")
                # Return mock content on error
                content = b"Error reading file"
                etag = "error-etag"
                last_modified = None

        # Build metadata
        metadata = {
            "path": path,
            "size": len(content),
            "content_type": "application/octet-stream",
            "etag": etag,
            "last_modified": last_modified.isoformat() if last_modified else None,
            "commit_sha": self.commit,
            "commit_metadata": self._commit_metadata
        }

        # Instrument if Briefcase client available
        if self.briefcase_client:
            self._record_access(path, metadata, start_time)

        if return_metadata:
            return content, metadata
        return content

    def _record_access(
        self,
        path: str,
        metadata: Dict,
        start_time: datetime
    ):
        """Record file access in current Briefcase span."""
        if not HAS_OTEL:
            logger.warning("OpenTelemetry not available, skipping instrumentation")
            return

        try:
            current_span = trace.get_current_span()
            if not current_span or not current_span.is_recording():
                return

            # Set commit-level attributes (once per span)
            current_span.set_attribute(
                lakefs_attrs.LAKEFS_COMMIT_SHA,
                self._commit_metadata["sha"]
            )
            current_span.set_attribute(
                lakefs_attrs.LAKEFS_COMMIT_BRANCH,
                self.branch
            )
            current_span.set_attribute(
                lakefs_attrs.LAKEFS_COMMIT_TIMESTAMP,
                self._commit_metadata["timestamp"]
            )
            current_span.set_attribute(
                lakefs_attrs.LAKEFS_REPOSITORY,
                self.repository
            )

            # Record file access event
            current_span.add_event(
                "lakefs.file_accessed",
                attributes={
                    lakefs_attrs.LAKEFS_FILE_PATH: path,
                    lakefs_attrs.LAKEFS_FILE_SIZE: metadata["size"],
                    lakefs_attrs.LAKEFS_FILE_MODIFIED: metadata["last_modified"] or "unknown",
                    lakefs_attrs.LAKEFS_FILE_HASH: metadata["etag"],
                    lakefs_attrs.LAKEFS_ACCESS_TIME: datetime.now().isoformat(),
                    "duration_ms": (datetime.now() - start_time).total_seconds() * 1000
                }
            )

            # Set per-artifact attribute for this specific file
            artifact_attr = f"{lakefs_attrs.LAKEFS_ARTIFACT_PREFIX}{path}"
            current_span.set_attribute(artifact_attr, self.commit)
        except Exception as e:
            logger.error(f"Failed to record access: {e}")

    def list_objects(self, prefix: str = "") -> list:
        """List objects in lakeFS with optional prefix filter."""
        if not self._has_lakefs or not self._lakefs_client:
            # Mock mode: return fake list
            mock_results = [{"path": f"{prefix}file1.txt"}, {"path": f"{prefix}file2.txt"}]
            # Still emit OTel events in mock mode for testing
            if self.briefcase_client and HAS_OTEL:
                try:
                    current_span = trace.get_current_span()
                    if current_span and current_span.is_recording():
                        current_span.add_event(
                            "lakefs.objects_listed",
                            attributes={
                                "prefix": prefix,
                                "count": len(mock_results),
                                lakefs_attrs.LAKEFS_COMMIT_SHA: self.commit
                            }
                        )
                except Exception:
                    pass
            return mock_results

        try:
            objects = self._lakefs_client.list_objects(
                repository=self.repository,
                ref=self.commit,
                prefix=prefix
            )
            results = objects.results if hasattr(objects, 'results') else objects
        except Exception as e:
            logger.error(f"Failed to list objects: {e}")
            # Return mock list on error
            return [{"path": f"{prefix}error.txt"}]

        # Instrument listing operation
        if self.briefcase_client and HAS_OTEL:
            try:
                current_span = trace.get_current_span()
                if current_span and current_span.is_recording():
                    current_span.add_event(
                        "lakefs.objects_listed",
                        attributes={
                            "prefix": prefix,
                            "count": len(results),
                            lakefs_attrs.LAKEFS_COMMIT_SHA: self.commit
                        }
                    )
            except Exception as e:
                logger.error(f"Failed to instrument list operation: {e}")

        return results

    def get_commit(self) -> str:
        """Get the current commit SHA."""
        return self.commit

    def object_exists(self, path: str) -> bool:
        """Check if an object exists in lakeFS."""
        if not self._has_lakefs or not self._lakefs_client:
            # Mock mode: always return True
            return True

        try:
            self._lakefs_client.stat_object(
                repository=self.repository,
                ref=self.commit,
                path=path
            )
            return True
        except:
            return False

    def upload_object(
        self,
        path: str,
        data: bytes,
        content_type: str = "application/octet-stream"
    ):
        """Upload an object to lakeFS."""
        if not self._has_lakefs or not self._lakefs_client:
            logger.info(f"Mock mode: Would upload {len(data)} bytes to {path}")
            return

        try:
            self._lakefs_client.upload_object(
                repository=self.repository,
                branch=self.branch,
                path=path,
                data=data,
                content_type=content_type
            )
        except Exception as e:
            logger.error(f"Failed to upload object {path}: {e}")
            raise


# Backwards compatibility alias
InstrumentedLakeFSClient = VersionedClient
