"""
Observability - Logging and Sync History

Provides:
- JSONL logging to lakehouse
- Sync history tracking
- Correlation ID propagation
"""

import json
import logging
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from dataclasses import dataclass, asdict, field

from .utils import get_logs_path, get_state_path, ensure_dir, load_yaml, file_exists


# =============================================================================
# LOGGING
# =============================================================================

class OneLakeHandler(logging.Handler):
    """Logging handler that writes to OneLake via notebookutils."""

    def __init__(self, log_path: str):
        super().__init__()
        self.log_path = log_path
        self._buffer: List[str] = []

    def emit(self, record):
        try:
            log_entry = {
                "ts": datetime.now(timezone.utc).isoformat(),
                "level": record.levelname,
                "correlation_id": getattr(record, "correlation_id", None),
                "event": record.getMessage(),
                "logger": record.name,
            }
            # Add extra fields if present
            for key in ["source", "resource", "rows", "duration_ms", "error"]:
                if hasattr(record, key):
                    log_entry[key] = getattr(record, key)

            line = json.dumps(log_entry, default=str)
            self._buffer.append(line)

            # Flush every 10 lines or on error
            if len(self._buffer) >= 10 or record.levelno >= logging.ERROR:
                self.flush()
        except Exception:
            pass

    def flush(self):
        if not self._buffer:
            return
        try:
            import notebookutils
            content = "\n".join(self._buffer) + "\n"
            notebookutils.fs.append(self.log_path, content, createFileIfNotExists=True)
            self._buffer = []
        except Exception:
            # Fallback to local file
            try:
                with open(self.log_path, "a") as f:
                    f.write("\n".join(self._buffer) + "\n")
                self._buffer = []
            except Exception:
                pass

    def close(self):
        self.flush()
        super().close()


class CorrelationAdapter(logging.LoggerAdapter):
    """Logger adapter that adds correlation_id to all log records."""

    def process(self, msg, kwargs):
        extra = kwargs.get("extra", {})
        extra["correlation_id"] = self.extra.get("correlation_id")
        kwargs["extra"] = extra
        return msg, kwargs


def setup_logging(
    source_name: str,
    correlation_id: Optional[str] = None,
    log_dir: Optional[str] = None,
) -> tuple:
    """
    Setup logging for a pipeline run.

    Args:
        source_name: Name of the source being synced
        correlation_id: Optional correlation ID (generated if not provided)
        log_dir: Optional log directory override

    Returns:
        Tuple of (logger, log_path, correlation_id)
    """
    if correlation_id is None:
        correlation_id = f"corr_{uuid.uuid4().hex[:12]}"

    if log_dir is None:
        log_dir = get_logs_path()

    ensure_dir(log_dir)

    timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    log_path = f"{log_dir}/{source_name}_{timestamp}.jsonl"

    # Create base logger
    logger_name = f"dlt_ingestion.{source_name}"
    base_logger = logging.getLogger(logger_name)
    base_logger.setLevel(logging.INFO)
    base_logger.handlers = []

    # Add OneLake handler
    handler = OneLakeHandler(log_path)
    handler.setLevel(logging.INFO)
    base_logger.addHandler(handler)
    base_logger.propagate = False

    # Wrap with correlation adapter
    logger = CorrelationAdapter(base_logger, {"correlation_id": correlation_id})

    logger.info(f"Logging initialized", extra={"source": source_name})
    print(f"Log file: {log_path}")
    print(f"Correlation ID: {correlation_id}")

    return logger, log_path, correlation_id


# =============================================================================
# SYNC HISTORY
# =============================================================================

@dataclass
class ResourceResult:
    """Result for a single resource sync."""
    resource_name: str
    status: str  # succeeded | failed | skipped
    rows_extracted: int = 0
    rows_loaded: int = 0
    duration_ms: int = 0
    error: Optional[str] = None


@dataclass
class SyncRecord:
    """Record of a sync execution."""
    sync_id: str
    source_name: str
    correlation_id: str
    started_at: str
    completed_at: Optional[str] = None
    status: str = "running"  # running | succeeded | failed | partial_failure
    trigger_type: str = "manual"  # scheduled | manual | api
    total_rows_extracted: int = 0
    total_rows_loaded: int = 0
    duration_ms: int = 0
    error_message: Optional[str] = None
    resource_results: Dict[str, Dict] = field(default_factory=dict)
    schema_changes: Optional[Dict] = None


class SyncHistoryTracker:
    """
    Tracks sync history in a JSON file.

    For simplicity, stores sync history in a JSON file in the lakehouse.
    Can be migrated to a Delta table later for better queryability.
    """

    def __init__(self, state_dir: Optional[str] = None):
        """
        Initialize sync history tracker.

        Args:
            state_dir: Directory for state files
        """
        self.state_dir = state_dir or get_state_path()
        self.history_file = f"{self.state_dir}/sync_history.json"
        ensure_dir(self.state_dir)

    def _load_history(self) -> List[Dict]:
        """Load existing sync history."""
        if file_exists(self.history_file):
            try:
                return load_yaml(self.history_file)  # YAML can parse JSON too
            except Exception:
                return []
        return []

    def _save_history(self, history: List[Dict]) -> None:
        """Save sync history."""
        try:
            import notebookutils
            content = json.dumps(history, indent=2, default=str)
            notebookutils.fs.put(self.history_file, content, overwrite=True)
        except ImportError:
            with open(self.history_file, "w") as f:
                json.dump(history, f, indent=2, default=str)

    def start_sync(
        self,
        source_name: str,
        correlation_id: str,
        trigger_type: str = "manual",
    ) -> SyncRecord:
        """
        Record the start of a sync.

        Args:
            source_name: Name of the source
            correlation_id: Correlation ID for this sync
            trigger_type: How the sync was triggered

        Returns:
            SyncRecord for this sync
        """
        sync_id = f"sync_{uuid.uuid4().hex[:12]}"
        record = SyncRecord(
            sync_id=sync_id,
            source_name=source_name,
            correlation_id=correlation_id,
            started_at=datetime.now(timezone.utc).isoformat(),
            trigger_type=trigger_type,
        )

        # Append to history
        history = self._load_history()
        history.append(asdict(record))
        self._save_history(history)

        return record

    def complete_sync(
        self,
        sync_record: SyncRecord,
        status: str,
        resource_results: Dict[str, ResourceResult],
        error_message: Optional[str] = None,
    ) -> SyncRecord:
        """
        Record the completion of a sync.

        Args:
            sync_record: The sync record to update
            status: Final status
            resource_results: Results per resource
            error_message: Error message if failed

        Returns:
            Updated SyncRecord
        """
        now = datetime.now(timezone.utc)
        started = datetime.fromisoformat(sync_record.started_at.replace("Z", "+00:00"))
        duration_ms = int((now - started).total_seconds() * 1000)

        sync_record.completed_at = now.isoformat()
        sync_record.status = status
        sync_record.duration_ms = duration_ms
        sync_record.error_message = error_message

        # Aggregate resource results
        sync_record.resource_results = {
            name: asdict(result) for name, result in resource_results.items()
        }
        sync_record.total_rows_extracted = sum(
            r.rows_extracted for r in resource_results.values()
        )
        sync_record.total_rows_loaded = sum(
            r.rows_loaded for r in resource_results.values()
        )

        # Update in history
        history = self._load_history()
        for i, h in enumerate(history):
            if h.get("sync_id") == sync_record.sync_id:
                history[i] = asdict(sync_record)
                break
        self._save_history(history)

        return sync_record

    def get_last_sync(self, source_name: str) -> Optional[Dict]:
        """
        Get the last sync record for a source.

        Args:
            source_name: Name of the source

        Returns:
            Last sync record or None
        """
        history = self._load_history()
        for record in reversed(history):
            if record.get("source_name") == source_name:
                return record
        return None

    def get_history(
        self,
        source_name: Optional[str] = None,
        limit: int = 100,
    ) -> List[Dict]:
        """
        Get sync history.

        Args:
            source_name: Optional filter by source
            limit: Maximum records to return

        Returns:
            List of sync records (newest first)
        """
        history = self._load_history()
        if source_name:
            history = [h for h in history if h.get("source_name") == source_name]
        return list(reversed(history))[:limit]
