# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Co-op Transport Layer

Handles export, import, and signature verification of co-op data bundles.

Transport modes:
  - Filesystem: Manual bundle sharing via .json files
  - Mesh: Automatic sharing via existing mesh gateway (future)
  - API: Reflection multi-tenant sharing (future)

Dependencies: None (pure stdlib, cryptography optional for signing)
"""

import hashlib
import json
import logging
from datetime import datetime
from pathlib import Path

logger = logging.getLogger(__name__)


class CoopTransport:
    """
    Export and import signed co-op data bundles.

    Bundles contain co-op contributions with cryptographic signatures
    for integrity verification.
    """

    def __init__(self, coop_dir: Path = None):
        if coop_dir is None:
            try:
                from familiar.core.paths import COOP_DIR

                self._coop_dir = COOP_DIR
            except ImportError:
                self._coop_dir = Path.home() / ".familiar" / "data" / "coop"
        else:
            self._coop_dir = coop_dir
        self._coop_dir.mkdir(parents=True, exist_ok=True)

    def export_bundle(self, coop_id: str) -> Path:
        """
        Export a signed co-op contribution bundle.

        Creates a JSON file containing:
          - Co-op manifest
          - This member's contributions
          - SHA-256 content hash
          - RSA signature (if keys available)

        Returns:
            Path to the exported bundle file
        """
        coop_path = self._coop_dir / coop_id
        manifest_path = coop_path / "manifest.json"

        if not manifest_path.exists():
            raise FileNotFoundError(f"Co-op {coop_id} not found")

        with open(manifest_path, encoding="utf-8") as f:
            manifest = json.load(f)

        # Collect this member's contributions
        genesis_hash = self._get_genesis_hash()
        contrib_file = coop_path / "contributions" / f"{genesis_hash}.jsonl"

        records = []
        if contrib_file.exists():
            with open(contrib_file, encoding="utf-8") as f:
                for line in f:
                    line = line.strip()
                    if line:
                        try:
                            records.append(json.loads(line))
                        except json.JSONDecodeError:
                            continue

        # Build bundle
        bundle = {
            "bundle_version": 1,
            "coop_id": coop_id,
            "coop_name": manifest.get("name", ""),
            "contributor": genesis_hash,
            "exported_at": datetime.now().isoformat(),
            "record_count": len(records),
            "records": records,
            "manifest_snapshot": manifest,
        }

        # Compute content hash
        content = json.dumps(bundle, sort_keys=True, default=str)
        content_hash = hashlib.sha256(content.encode()).hexdigest()
        bundle["content_hash"] = content_hash

        # Sign if keys available
        signature = self._sign_content(content_hash)
        bundle["signature"] = signature

        # Write bundle
        bundle_path = coop_path / f"bundle_{genesis_hash}_{datetime.now().strftime('%Y%m%d')}.json"
        with open(bundle_path, "w", encoding="utf-8") as f:
            json.dump(bundle, f, indent=2, default=str)

        logger.info("Exported bundle: %s (%d records)", bundle_path.name, len(records))
        return bundle_path

    def import_bundle(self, bundle_path: str) -> dict:
        """
        Import and verify a co-op contribution bundle.

        Verifies content hash and signature, then imports records
        into the local co-op directory.

        Returns dict with import results.
        """
        path = Path(bundle_path)
        if not path.exists():
            return {"error": f"Bundle not found: {bundle_path}"}

        with open(path, encoding="utf-8") as f:
            bundle = json.load(f)

        coop_id = bundle.get("coop_id")
        if not coop_id:
            return {"error": "Invalid bundle: missing coop_id"}

        # Verify content hash
        stored_hash = bundle.pop("content_hash", "")
        stored_sig = bundle.pop("signature", "")
        content = json.dumps(bundle, sort_keys=True, default=str)
        computed_hash = hashlib.sha256(content.encode()).hexdigest()

        bundle["content_hash"] = stored_hash
        bundle["signature"] = stored_sig

        if stored_hash and computed_hash != stored_hash:
            return {"error": "Content hash mismatch — bundle may be tampered"}

        # Import records
        contributor = bundle.get("contributor", "unknown")
        records = bundle.get("records", [])

        if not records:
            return {"imported": 0, "contributor": contributor}

        # Ensure co-op directory exists
        coop_path = self._coop_dir / coop_id
        coop_path.mkdir(parents=True, exist_ok=True)

        # Save manifest if we don't have one
        manifest_path = coop_path / "manifest.json"
        if not manifest_path.exists():
            snapshot = bundle.get("manifest_snapshot", {})
            if snapshot:
                with open(manifest_path, "w", encoding="utf-8") as f:
                    json.dump(snapshot, f, indent=2, default=str)

        # Write contribution
        contrib_dir = coop_path / "contributions"
        contrib_dir.mkdir(parents=True, exist_ok=True)

        contrib_file = contrib_dir / f"{contributor}.jsonl"
        with open(contrib_file, "w", encoding="utf-8") as f:
            for r in records:
                f.write(json.dumps(r, default=str) + "\n")

        logger.info("Imported %d records from %s into co-op %s", len(records), contributor, coop_id)
        return {
            "imported": len(records),
            "contributor": contributor,
            "coop_id": coop_id,
            "hash_verified": stored_hash == computed_hash if stored_hash else False,
        }

    def verify_bundle_signature(self, bundle_path: str) -> bool:
        """
        Verify the RSA signature of a bundle.

        Returns True if signature is valid, False otherwise.
        """
        path = Path(bundle_path)
        if not path.exists():
            return False

        with open(path, encoding="utf-8") as f:
            bundle = json.load(f)

        content_hash = bundle.get("content_hash", "")
        signature = bundle.get("signature", "")

        if not content_hash or not signature:
            return False

        # Look up contributor's public key from manifest
        contributor = bundle.get("contributor", "")
        manifest = bundle.get("manifest_snapshot", {})
        members = manifest.get("members", [])

        pub_pem = ""
        for m in members:
            if m.get("genesis_hash") == contributor:
                pub_pem = m.get("public_key_pem", "")
                break

        if not pub_pem:
            logger.debug("No public key found for contributor %s", contributor)
            return False

        try:
            from cryptography.hazmat.primitives import hashes, serialization
            from cryptography.hazmat.primitives.asymmetric import padding

            public_key = serialization.load_pem_public_key(pub_pem.encode())
            public_key.verify(
                bytes.fromhex(signature),
                content_hash.encode(),
                padding.PKCS1v15(),
                hashes.SHA256(),
            )
            return True
        except Exception:
            logger.debug("Signature verification failed", exc_info=True)
            return False

    def _get_genesis_hash(self) -> str:
        """Get this instance's genesis hash."""
        try:
            from familiar.skills.training.bridge import get_chain_status

            status = get_chain_status()
            return status.get("genesis_hash", "") or "local"
        except Exception:
            return "local"

    def _sign_content(self, content_hash: str) -> str:
        """Sign content hash with RSA private key. Returns hex signature or empty string."""
        try:
            from cryptography.hazmat.primitives import hashes
            from cryptography.hazmat.primitives.asymmetric import padding

            from familiar.core.genesis import load_or_create_keypair
            from familiar.core.paths import CHAIN_DIR

            key_dir = CHAIN_DIR / "keys"
            private_key, _ = load_or_create_keypair(key_dir)

            signature = private_key.sign(
                content_hash.encode(),
                padding.PKCS1v15(),
                hashes.SHA256(),
            )
            return signature.hex()
        except Exception:
            logger.debug("Signing not available", exc_info=True)
            return ""
