# 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

"""
Model Card Generator

Auto-generates Markdown model cards with full provenance: contributors,
consent types, quality metrics, hyperparameters, and chain verification status.

Follows the Model Card standard (Mitchell et al., 2019) adapted for
chain-of-provenance training data.

Dependencies: None (pure stdlib)
"""

import logging
from datetime import datetime
from pathlib import Path

logger = logging.getLogger(__name__)


def generate_model_card(
    model_name: str,
    training_run: dict = None,
    dataset_meta: dict = None,
    chain_status: dict = None,
    coop_manifest: dict = None,
) -> str:
    """
    Generate a Markdown model card with full provenance.

    Args:
        model_name: Display name for the model
        training_run: TrainingResult dict from model_training
        dataset_meta: Dataset metadata from export_dataset
        chain_status: Chain verification status from bridge
        coop_manifest: Optional co-op manifest for cooperative models

    Returns:
        Complete model card as Markdown string
    """
    now = datetime.now().isoformat()[:19]
    sections = []

    # Header
    sections.append(f"# Model Card: {model_name}")
    sections.append("")
    sections.append(f"*Generated: {now}*")
    sections.append("")

    # Model Details
    sections.append("## Model Details")
    sections.append("")

    if training_run:
        config = training_run.get("config", {})
        sections.append(f"- **Base model**: {config.get('base_model', 'unknown')}")
        sections.append(f"- **Training backend**: {training_run.get('backend', 'unknown')}")
        sections.append(f"- **Model hash**: `{training_run.get('model_hash', 'n/a')[:16]}...`")
        sections.append(f"- **Training date**: {training_run.get('started_at', now)[:10]}")
    else:
        sections.append(f"- **Model name**: {model_name}")
        sections.append(f"- **Date**: {now[:10]}")

    sections.append("")

    # Training Data
    sections.append("## Training Data")
    sections.append("")
    sections.append("All training data is ethically sourced with verified provenance and consent.")
    sections.append("")

    if dataset_meta:
        sections.append(f"- **Total examples**: {dataset_meta.get('total_examples', 0)}")
        sections.append(f"- **Export format**: {dataset_meta.get('format', 'alpaca')}")
        sections.append(f"- **Min quality threshold**: {dataset_meta.get('min_quality', 0.5)}")
        sections.append(f"- **Avg quality score**: {dataset_meta.get('avg_quality_score', 0)}")
        fp = dataset_meta.get("export_fingerprint", "")
        if fp:
            sections.append(f"- **Dataset fingerprint**: `{fp[:16]}...`")

        # Sources
        source_counts = dataset_meta.get("source_counts", {})
        if source_counts:
            sections.append("")
            sections.append("### Data Sources")
            sections.append("")
            for src, count in source_counts.items():
                sections.append(f"- {src}: {count} examples")

        # Provenance
        prov = dataset_meta.get("provenance_summary", {})
        if prov:
            sections.append("")
            sections.append("### Provenance")
            sections.append("")
            authors = prov.get("authors", [])
            if authors:
                sections.append(f"- **Contributors**: {', '.join(str(a) for a in authors)}")
            consent_types = prov.get("all_consent_types", [])
            if consent_types:
                sections.append(f"- **Consent types**: {', '.join(consent_types)}")
            sections.append(f"- **License**: {prov.get('license', 'proprietary')}")
    else:
        sections.append("*No dataset metadata available.*")

    sections.append("")

    # Hyperparameters
    if training_run:
        config = training_run.get("config", {})
        sections.append("## Hyperparameters")
        sections.append("")
        sections.append(f"- **LoRA rank**: {config.get('lora_rank', 8)}")
        sections.append(f"- **LoRA alpha**: {config.get('lora_alpha', 16)}")
        sections.append(f"- **Epochs**: {config.get('epochs', 3)}")
        sections.append(f"- **Learning rate**: {config.get('learning_rate', 2e-4)}")
        sections.append(f"- **Batch size**: {config.get('batch_size', 4)}")
        sections.append(f"- **Max sequence length**: {config.get('max_seq_length', 2048)}")
        sections.append("")

    # Metrics
    if training_run and training_run.get("metrics"):
        sections.append("## Training Metrics")
        sections.append("")
        for k, v in training_run["metrics"].items():
            sections.append(f"- **{k}**: {v}")
        sections.append("")

    # Chain Verification
    sections.append("## Chain of Provenance")
    sections.append("")

    if chain_status:
        if chain_status.get("initialized"):
            gh = chain_status.get("genesis_hash", "")
            sections.append(f"- **Genesis hash**: `{gh[:16]}...`")
            sections.append(f"- **Chain length**: {chain_status.get('chain_length', 0)} blocks")
            integrity = chain_status.get("integrity", {})
            if integrity:
                status = "VALID" if integrity.get("valid") else "INVALID"
                sections.append(f"- **Integrity**: {status}")
            if training_run and training_run.get("chain_block_hash"):
                sections.append(
                    f"- **Training block**: `{training_run['chain_block_hash'][:16]}...`"
                )
        else:
            sections.append("*Chain not initialized — provenance unverified.*")
    else:
        sections.append("*Chain status not available.*")

    sections.append("")

    # Co-op Information
    if coop_manifest:
        sections.append("## Cooperative Training")
        sections.append("")
        sections.append(f"- **Co-op name**: {coop_manifest.get('name', '')}")
        sections.append(f"- **Co-op ID**: {coop_manifest.get('coop_id', '')}")
        members = coop_manifest.get("members", [])
        sections.append(f"- **Members**: {len(members)}")

        contrib_stats = coop_manifest.get("contribution_stats", {})
        if contrib_stats:
            total = sum(s.get("records_contributed", 0) for s in contrib_stats.values())
            sections.append(f"- **Total contributions**: {total} records")
            sections.append(f"- **Contributors**: {len(contrib_stats)}")
        sections.append("")

    # Ethical Considerations
    sections.append("## Ethical Considerations")
    sections.append("")
    sections.append("This model was trained exclusively on ethically-sourced data:")
    sections.append("")
    sections.append("- No scraped or unlicensed data")
    sections.append("- All contributors gave explicit consent")
    sections.append("- Full chain of provenance from data creation to model deployment")
    sections.append("- Right to erasure honored — contributors can request data removal")
    sections.append("- Quality-gated — only data meeting minimum thresholds included")
    sections.append("")

    # Footer
    sections.append("---")
    sections.append("")
    sections.append(
        "*Generated by [Familiar](https://github.com/omegcrash/familiar) "
        "chain-of-provenance pipeline.*"
    )

    return "\n".join(sections)


def save_model_card(
    model_name: str,
    output_path: str = None,
    **kwargs,
) -> str:
    """
    Generate and save a model card to disk.

    Returns the path where the card was saved.
    """
    card = generate_model_card(model_name, **kwargs)

    if output_path:
        path = Path(output_path)
    else:
        try:
            from familiar.core.paths import DATA_DIR

            path = DATA_DIR / "training" / "models" / f"{model_name}_model_card.md"
        except ImportError:
            path = (
                Path.home()
                / ".familiar"
                / "data"
                / "training"
                / "models"
                / f"{model_name}_model_card.md"
            )

    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(card, encoding="utf-8")
    logger.info("Model card saved to %s", path)
    return str(path)
