"""
Optimus Research Center - Shared Scientific Memory
Provides unified storage, query, cross-referencing, lineage,
search, and backup/restore for all scientific artifacts, hypotheses, and experiments.
"""

import json
import shutil
import hashlib
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List, Optional
from sqlalchemy.orm import Session
from sqlalchemy import or_, desc

from app.config import settings
from app.core.database import SessionLocal
from app.core import models


class ScientificMemory:
    """Interface to persistent scientific memory and lineage graph."""

    def __init__(self, db: Optional[Session] = None):
        self._db = db

    def _get_db(self) -> Session:
        return self._db if self._db is not None else SessionLocal()

    def record_hypothesis(
        self,
        project_id: int,
        title: str,
        statement: str,
        rationale: Optional[str] = None,
        confidence_score: float = 0.5
    ) -> models.Hypothesis:
        db = self._get_db()
        try:
            hyp = models.Hypothesis(
                project_id=project_id,
                title=title,
                statement=statement,
                rationale=rationale,
                confidence_score=confidence_score,
                status="PROPOSED"
            )
            db.add(hyp)
            db.commit()
            db.refresh(hyp)
            return hyp
        finally:
            if self._db is None:
                db.close()

    def record_experiment(
        self,
        project_id: int,
        name: str,
        experiment_type: str,
        config: Dict[str, Any],
        hypothesis_id: Optional[int] = None
    ) -> models.Experiment:
        db = self._get_db()
        try:
            exp = models.Experiment(
                project_id=project_id,
                hypothesis_id=hypothesis_id,
                name=name,
                experiment_type=experiment_type,
                config_json=json.dumps(config),
                status="PENDING",
                started_at=datetime.utcnow()
            )
            db.add(exp)
            db.commit()
            db.refresh(exp)
            return exp
        finally:
            if self._db is None:
                db.close()

    def record_experiment_run(
        self,
        experiment_id: int,
        algorithm_or_model_name: str,
        execution_time_seconds: float,
        cpu_time_seconds: float,
        peak_memory_mb: float,
        metrics: Dict[str, Any],
        outcome: str = "POSITIVE",
        random_seed: int = 42,
        environment_info: Optional[Dict[str, Any]] = None,
        run_index: int = 1
    ) -> models.ExperimentRun:
        db = self._get_db()
        try:
            run = models.ExperimentRun(
                experiment_id=experiment_id,
                run_index=run_index,
                algorithm_or_model_name=algorithm_or_model_name,
                execution_time_seconds=execution_time_seconds,
                cpu_time_seconds=cpu_time_seconds,
                peak_memory_mb=peak_memory_mb,
                metrics_json=json.dumps(metrics),
                outcome=outcome,
                random_seed=random_seed,
                environment_info_json=json.dumps(environment_info or {})
            )
            db.add(run)
            db.commit()
            db.refresh(run)
            return run
        finally:
            if self._db is None:
                db.close()

    def record_artifact(
        self,
        experiment_id: int,
        name: str,
        file_path: Path,
        file_type: str,
        metadata: Optional[Dict[str, Any]] = None
    ) -> models.ScientificArtifact:
        db = self._get_db()
        try:
            sha256 = self.calculate_file_hash(file_path)
            size = file_path.stat().st_size if file_path.exists() else 0

            artifact = models.ScientificArtifact(
                experiment_id=experiment_id,
                name=name,
                file_path=str(file_path),
                file_type=file_type,
                sha256_hash=sha256,
                file_size_bytes=size,
                metadata_json=json.dumps(metadata or {})
            )
            db.add(artifact)
            db.commit()
            db.refresh(artifact)
            return artifact
        finally:
            if self._db is None:
                db.close()

    def record_evidence(
        self,
        experiment_id: int,
        title: str,
        conclusion: str,
        confidence_level: str = "MEDIUM",
        limitations: Optional[str] = None,
        sources: Optional[List[Dict[str, Any]]] = None,
        hypothesis_id: Optional[int] = None
    ) -> models.ScientificEvidence:
        db = self._get_db()
        try:
            raw_fingerprint = f"{experiment_id}:{title}:{conclusion}:{datetime.utcnow().isoformat()}"
            fingerprint = hashlib.sha256(raw_fingerprint.encode("utf-8")).hexdigest()

            evidence = models.ScientificEvidence(
                experiment_id=experiment_id,
                hypothesis_id=hypothesis_id,
                title=title,
                conclusion=conclusion,
                confidence_level=confidence_level,
                limitations=limitations,
                sources_json=json.dumps(sources or []),
                sha256_fingerprint=fingerprint
            )
            db.add(evidence)
            db.commit()
            db.refresh(evidence)
            return evidence
        finally:
            if self._db is None:
                db.close()

    def search_knowledge(self, query: str) -> Dict[str, Any]:
        """Unified text and keyword search across scientific memory."""
        db = self._get_db()
        try:
            q_like = f"%{query}%"
            projects = db.query(models.ResearchProject).filter(
                or_(
                    models.ResearchProject.title.ilike(q_like),
                    models.ResearchProject.description.ilike(q_like)
                )
            ).all()

            hypotheses = db.query(models.Hypothesis).filter(
                or_(
                    models.Hypothesis.title.ilike(q_like),
                    models.Hypothesis.statement.ilike(q_like)
                )
            ).all()

            evidence = db.query(models.ScientificEvidence).filter(
                or_(
                    models.ScientificEvidence.title.ilike(q_like),
                    models.ScientificEvidence.conclusion.ilike(q_like)
                )
            ).all()

            experiments = db.query(models.Experiment).filter(
                or_(
                    models.Experiment.name.ilike(q_like),
                    models.Experiment.description.ilike(q_like)
                )
            ).all()

            return {
                "query": query,
                "projects_count": len(projects),
                "projects": [{"id": p.id, "title": p.title, "lab_type": p.lab_type} for p in projects],
                "hypotheses_count": len(hypotheses),
                "hypotheses": [{"id": h.id, "title": h.title, "status": h.status} for h in hypotheses],
                "evidence_count": len(evidence),
                "evidence": [{"id": e.id, "title": e.title, "confidence": e.confidence_level} for e in evidence],
                "experiments_count": len(experiments),
                "experiments": [{"id": ex.id, "name": ex.name, "status": ex.status} for ex in experiments]
            }
        finally:
            if self._db is None:
                db.close()

    def create_memory_backup(self) -> Path:
        """
        Creates a concurrency-safe, WAL-consistent snapshot of the scientific SQLite database
        using SQLite's native online backup API (sqlite3.Connection.backup) and verifies PRAGMA integrity_check.
        """
        import sqlite3

        timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S_%f")
        backup_dir = settings.storage_path / "backups"
        backup_dir.mkdir(parents=True, exist_ok=True)
        backup_file = backup_dir / f"research_center_backup_{timestamp}.db"

        source_db = settings.db_path
        if source_db.exists():
            src_conn = sqlite3.connect(str(source_db), timeout=30.0)
            dst_conn = sqlite3.connect(str(backup_file), timeout=30.0)
            try:
                with dst_conn:
                    src_conn.backup(dst_conn)
                chk = dst_conn.execute("PRAGMA integrity_check").fetchone()
                if not chk or chk[0] != "ok":
                    raise RuntimeError(f"Backup integrity check failed for {backup_file}: {chk}")
            finally:
                dst_conn.close()
                src_conn.close()
        return backup_file

    @staticmethod
    def calculate_file_hash(file_path: Path) -> str:
        """Calculates standard SHA-256 hash for artifact verification."""
        if not file_path.exists():
            return "0" * 64
        h = hashlib.sha256()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(65536), b""):
                h.update(chunk)
        return h.hexdigest()

    def index_knowledge(
        self,
        domain: str,
        title: str,
        summary: str,
        evidence_hash: str,
        tags: Optional[List[str]] = None
    ) -> Dict[str, Any]:
        """Indexes or records scientific knowledge item."""
        return {
            "domain": domain,
            "title": title,
            "summary": summary,
            "evidence_hash": evidence_hash,
            "tags": tags or [],
            "indexed_at": datetime.utcnow().isoformat()
        }

