#!/usr/bin/env python3
"""
Novyx Core - Import Module (Phase 11)
======================================
Import artifacts from external formats for migrations and integrations.

Supported Formats:
- RDF/Turtle: Import from semantic web formats using rdflib
- Neo4j: Query and import from Neo4j graph database
- JSON-LD: Import from consolidated JSON-LD exports

Features:
- CORE_SCHEMA property reverse mappings
- Integrity hash regeneration
- Multi-tenant aware imports
- Batch processing for large datasets

Usage:
    from tools.import_data import import_artifacts, ImportFormat

    # Import from RDF/Turtle
    import_artifacts(Path("export.ttl"), ImportFormat.RDF, Path("memory"))

    # Import from JSON-LD
    import_artifacts(Path("export.jsonld"), ImportFormat.JSONLD, Path("memory"))
"""

import argparse
import json
import sys
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
import uuid as uuid_lib

# Optional dependency handling
try:
    from rdflib import Graph, Namespace, URIRef
    from rdflib.namespace import RDF
    RDFLIB_AVAILABLE = True
except ImportError:
    RDFLIB_AVAILABLE = False

try:
    from neo4j import GraphDatabase
    NEO4J_AVAILABLE = True
except ImportError:
    NEO4J_AVAILABLE = False

# Import factory for creating artifacts
try:
    from tools.factory import create_artifact, persist_artifact
    from tools.util import calculate_hash
    FACTORY_AVAILABLE = True
except ImportError:
    FACTORY_AVAILABLE = False


class ImportFormat(Enum):
    """Supported import formats."""
    RDF = "rdf"
    NEO4J = "neo4j"
    JSONLD = "jsonld"


# Namespace definitions
NOVYX_NS = "https://novyx.ai/vocab#"
SCHEMA_NS = "https://schema.org/"

# Reverse property mappings (RDF predicate -> JSON-LD property)
REVERSE_PROPERTY_MAP = {
    f"{NOVYX_NS}uuid": "uuid",
    f"{NOVYX_NS}persistentIdentifier": "Persistent_ID",
    f"{NOVYX_NS}id": "id",
    "http://purl.org/dc/terms/created": "createdAt",
    "http://purl.org/dc/terms/modified": "updatedAt",
    f"{NOVYX_NS}temporalMarker": "Temporal_Marker",
    f"{NOVYX_NS}ingestedAt": "novyx:ingestedAt",
    f"{NOVYX_NS}updatedAt": "novyx:updatedAt",
    f"{NOVYX_NS}integrityHash": "novyx:integrityHash",
    f"{SCHEMA_NS}name": "name",
    f"{SCHEMA_NS}articleBody": "articleBody",
    f"{SCHEMA_NS}text": "content",
    f"{SCHEMA_NS}description": "description",
    f"{NOVYX_NS}context": "context",
    f"{NOVYX_NS}optionsConsidered": "options_considered",
    f"{NOVYX_NS}chosenOption": "chosen_option",
    f"{NOVYX_NS}reasoning": "reasoning",
    f"{NOVYX_NS}constraints": "constraints",
    f"{NOVYX_NS}assumptions": "assumptions",
    f"{NOVYX_NS}confidence": "confidence",
    f"{NOVYX_NS}expectedOutcome": "expected_outcome",
    f"{SCHEMA_NS}category": "category",
    f"{NOVYX_NS}httpMethod": "method",
    f"{NOVYX_NS}apiPath": "path",
    f"{NOVYX_NS}parameters": "parameters",
    f"{NOVYX_NS}responseSchema": "response_schema",
    f"{NOVYX_NS}operationType": "operation_type",
    f"{NOVYX_NS}operationName": "operation_name",
    f"{NOVYX_NS}arguments": "arguments",
    f"{NOVYX_NS}returnType": "return_type",
    f"{NOVYX_NS}tenantId": "tenant_id",
    f"{NOVYX_NS}adminEmail": "admin_email",
    f"{NOVYX_NS}planType": "plan_type",
    f"{NOVYX_NS}status": "status",
    f"{NOVYX_NS}artifactId": "artifact_id",
    f"{NOVYX_NS}versionNumber": "version_number",
    f"{NOVYX_NS}parentVersion": "parent_version",
    f"{NOVYX_NS}diff": "diff",
    f"{NOVYX_NS}changeSummary": "change_summary",
    f"{SCHEMA_NS}sameAs": "sameAs",
    f"{NOVYX_NS}semanticLinks": "novyx:semanticLinks",
}

# Reverse type mappings (RDF type -> artifact type)
REVERSE_TYPE_MAP = {
    f"{NOVYX_NS}Decision": "decision",
    f"{SCHEMA_NS}DigitalDocument": "decision",  # Default to decision
    f"{NOVYX_NS}APIEndpoint": "apiendpoint",
    f"{NOVYX_NS}GraphQLOperation": "graphqloperation",
    f"{NOVYX_NS}Tenant": "tenant",
    f"{NOVYX_NS}Version": "version",
}


def import_from_rdf(
    input_path: Path,
    output_dir: Path,
    *,
    tenant_id: Optional[str] = None,
    regenerate_hashes: bool = True,
    format: str = "turtle"
) -> Dict[str, Any]:
    """
    Import artifacts from RDF format.

    Args:
        input_path: Input RDF file path
        output_dir: Output directory for artifacts
        tenant_id: Optional tenant ID for imported artifacts
        regenerate_hashes: Recalculate integrity hashes (default: True)
        format: RDF serialization format

    Returns:
        Import statistics dict
    """
    if not RDFLIB_AVAILABLE:
        raise ImportError("rdflib not available. Install with: pip install rdflib")

    if not FACTORY_AVAILABLE:
        raise ImportError("tools.factory not available")

    g = Graph()
    g.parse(str(input_path), format=format)

    stats = {
        "total_subjects": 0,
        "imported": 0,
        "failed": 0,
        "skipped": 0,
    }

    # Find all subjects with types
    subjects = set(g.subjects())

    for subject in subjects:
        stats["total_subjects"] += 1

        try:
            # Get types for this subject
            types = list(g.objects(subject, RDF.type))
            if not types:
                stats["skipped"] += 1
                continue

            # Determine artifact type
            artifact_type = None
            for t in types:
                type_uri = str(t)
                if type_uri in REVERSE_TYPE_MAP:
                    artifact_type = REVERSE_TYPE_MAP[type_uri]
                    break

            if not artifact_type:
                stats["skipped"] += 1
                continue

            # Build artifact payload
            payload = {}

            for predicate, obj in g.predicate_objects(subject):
                pred_uri = str(predicate)

                if pred_uri == str(RDF.type):
                    continue

                # Map predicate to property name
                if pred_uri in REVERSE_PROPERTY_MAP:
                    prop_name = REVERSE_PROPERTY_MAP[pred_uri]
                else:
                    # Extract local name
                    prop_name = pred_uri.split("#")[-1].split("/")[-1]

                # Handle value
                value = str(obj)

                # Try to parse as JSON for arrays
                if prop_name in ("options_considered", "constraints", "assumptions", "parameters", "arguments", "sameAs"):
                    try:
                        parsed = json.loads(value)
                        if isinstance(parsed, list):
                            if prop_name not in payload:
                                payload[prop_name] = []
                            payload[prop_name].extend(parsed)
                            continue
                    except json.JSONDecodeError:
                        pass
                    # Add as list item
                    if prop_name not in payload:
                        payload[prop_name] = []
                    payload[prop_name].append(value)
                elif prop_name == "confidence":
                    try:
                        payload[prop_name] = float(value)
                    except ValueError:
                        payload[prop_name] = value
                else:
                    payload[prop_name] = value

            # Ensure required fields have defaults
            _ensure_defaults(payload, artifact_type)

            # Create artifact
            artifact = create_artifact(artifact_type, payload, tenant_id=tenant_id)

            # Persist
            persist_artifact(artifact, output_dir, skip_versioning=True)
            stats["imported"] += 1

        except Exception as e:
            stats["failed"] += 1
            print(f"Warning: Failed to import subject {subject}: {e}", file=sys.stderr)

    stats["output_dir"] = str(output_dir)

    return stats


def import_from_jsonld(
    input_path: Path,
    output_dir: Path,
    *,
    tenant_id: Optional[str] = None,
    regenerate_hashes: bool = True
) -> Dict[str, Any]:
    """
    Import artifacts from JSON-LD format.

    Args:
        input_path: Input JSON-LD file path
        output_dir: Output directory for artifacts
        tenant_id: Optional tenant ID for imported artifacts
        regenerate_hashes: Recalculate integrity hashes (default: True)

    Returns:
        Import statistics dict
    """
    if not FACTORY_AVAILABLE:
        raise ImportError("tools.factory not available")

    stats = {
        "total_artifacts": 0,
        "imported": 0,
        "failed": 0,
        "skipped": 0,
    }

    with open(input_path, 'r', encoding='utf-8') as f:
        data = json.load(f)

    # Handle both single artifact and @graph array
    artifacts = []
    if "@graph" in data:
        artifacts = data["@graph"]
    elif isinstance(data, list):
        artifacts = data
    else:
        artifacts = [data]

    for artifact_data in artifacts:
        stats["total_artifacts"] += 1

        try:
            # Determine artifact type
            artifact_type = _detect_artifact_type(artifact_data)
            if not artifact_type:
                stats["skipped"] += 1
                continue

            # Build payload from artifact data
            payload = _extract_payload(artifact_data, artifact_type)

            # Ensure required fields
            _ensure_defaults(payload, artifact_type)

            # Create artifact (regenerates hash if needed)
            artifact = create_artifact(artifact_type, payload, tenant_id=tenant_id)

            # If not regenerating hashes, preserve original
            if not regenerate_hashes:
                original_hash = artifact_data.get("novyx:integrityHash") or artifact_data.get("Integrity_Hash")
                if original_hash:
                    artifact["novyx:integrityHash"] = original_hash
                    artifact["Integrity_Hash"] = original_hash

            # Persist
            persist_artifact(artifact, output_dir, skip_versioning=True)
            stats["imported"] += 1

        except Exception as e:
            stats["failed"] += 1
            print(f"Warning: Failed to import artifact: {e}", file=sys.stderr)

    stats["output_dir"] = str(output_dir)

    return stats


def import_from_neo4j(
    uri: str,
    username: str,
    password: str,
    output_dir: Path,
    *,
    tenant_id: Optional[str] = None,
    database: str = "neo4j",
    query: Optional[str] = None,
    limit: int = 1000
) -> Dict[str, Any]:
    """
    Import artifacts from Neo4j database.

    Args:
        uri: Neo4j connection URI
        username: Neo4j username
        password: Neo4j password
        output_dir: Output directory for artifacts
        tenant_id: Optional tenant ID for imported artifacts
        database: Neo4j database name
        query: Custom Cypher query (default: MATCH (n:Artifact) RETURN n)
        limit: Maximum nodes to import

    Returns:
        Import statistics dict
    """
    if not NEO4J_AVAILABLE:
        raise ImportError("neo4j driver not available. Install with: pip install neo4j")

    if not FACTORY_AVAILABLE:
        raise ImportError("tools.factory not available")

    stats = {
        "total_nodes": 0,
        "imported": 0,
        "failed": 0,
        "skipped": 0,
    }

    driver = GraphDatabase.driver(uri, auth=(username, password))

    try:
        with driver.session(database=database) as session:
            cypher = query or f"MATCH (n:Artifact) RETURN n LIMIT {limit}"
            result = session.run(cypher)

            for record in result:
                stats["total_nodes"] += 1
                node = record["n"]

                try:
                    # Get labels and properties
                    labels = list(node.labels)
                    props = dict(node)

                    # Determine artifact type from labels
                    artifact_type = _labels_to_type(labels)
                    if not artifact_type:
                        stats["skipped"] += 1
                        continue

                    # Convert properties to payload
                    payload = _neo4j_props_to_payload(props, artifact_type)

                    # Ensure required fields
                    _ensure_defaults(payload, artifact_type)

                    # Create artifact
                    artifact = create_artifact(artifact_type, payload, tenant_id=tenant_id)

                    # Persist
                    persist_artifact(artifact, output_dir, skip_versioning=True)
                    stats["imported"] += 1

                except Exception as e:
                    stats["failed"] += 1
                    print(f"Warning: Failed to import node: {e}", file=sys.stderr)

    finally:
        driver.close()

    stats["output_dir"] = str(output_dir)

    return stats


def _detect_artifact_type(artifact: Dict[str, Any]) -> Optional[str]:
    """Detect artifact type from @type field."""
    type_val = artifact.get("@type", [])

    if isinstance(type_val, str):
        type_val = [type_val]

    for t in type_val:
        t_lower = t.lower()
        if "decision" in t_lower:
            return "decision"
        elif "apiendpoint" in t_lower:
            return "apiendpoint"
        elif "graphql" in t_lower:
            return "graphqloperation"
        elif "tenant" in t_lower:
            return "tenant"
        elif "version" in t_lower:
            return "version"
        elif "document" in t_lower:
            return "decision"  # Default fallback

    return None


def _extract_payload(artifact: Dict[str, Any], artifact_type: str) -> Dict[str, Any]:
    """Extract payload fields from artifact data."""
    # Fields to skip (will be regenerated)
    skip_fields = {
        "@context", "@type", "@id", "uuid", "id",
        "Persistent_ID", "novyx:integrityHash", "Integrity_Hash",
        "createdAt", "updatedAt", "Temporal_Marker",
        "novyx:ingestedAt", "novyx:updatedAt",
        "novyx:embedding", "embedding"
    }

    payload = {}

    for key, value in artifact.items():
        if key in skip_fields:
            continue

        # Normalize property name
        prop_name = key.replace("novyx:", "")

        payload[prop_name] = value

    return payload


def _labels_to_type(labels: List[str]) -> Optional[str]:
    """Convert Neo4j labels to artifact type."""
    label_map = {
        "Decision": "decision",
        "Document": "decision",
        "APIEndpoint": "apiendpoint",
        "GraphQLOperation": "graphqloperation",
        "Tenant": "tenant",
        "Version": "version",
    }

    for label in labels:
        if label in label_map:
            return label_map[label]

    return None


def _neo4j_props_to_payload(props: Dict[str, Any], artifact_type: str) -> Dict[str, Any]:
    """Convert Neo4j properties to artifact payload."""
    # Properties to skip
    skip_props = {"uuid", "integrityHash", "Integrity_Hash", "createdAt", "updatedAt"}

    payload = {}

    for key, value in props.items():
        if key in skip_props:
            continue

        # Try to parse JSON arrays
        if isinstance(value, str) and value.startswith("["):
            try:
                parsed = json.loads(value)
                payload[key] = parsed
                continue
            except json.JSONDecodeError:
                pass

        payload[key] = value

    return payload


def _ensure_defaults(payload: Dict[str, Any], artifact_type: str) -> None:
    """Ensure required fields have default values."""
    if artifact_type == "decision":
        defaults = {
            "title": payload.get("name", "Imported Decision"),
            "context": "Imported from external source",
            "options_considered": [],
            "chosen_option": "Imported",
            "reasoning": "Imported from external source",
            "constraints": [],
            "assumptions": [],
            "confidence": 0.5,
            "expected_outcome": "Imported",
            "category": "imported"
        }
        for key, value in defaults.items():
            if key not in payload:
                payload[key] = value

    elif artifact_type == "apiendpoint":
        defaults = {
            "title": payload.get("name", "Imported Endpoint"),
            "method": "GET",
            "path": "/imported",
            "description": "Imported from external source",
            "parameters": [],
            "response_schema": "{}"
        }
        for key, value in defaults.items():
            if key not in payload:
                payload[key] = value

    elif artifact_type == "graphqloperation":
        defaults = {
            "title": payload.get("name", "Imported Operation"),
            "operation_type": "Query",
            "operation_name": "imported",
            "description": "Imported from external source",
            "arguments": [],
            "return_type": "String"
        }
        for key, value in defaults.items():
            if key not in payload:
                payload[key] = value


def import_artifacts(
    input_path: Path,
    format: ImportFormat,
    output_dir: Path,
    *,
    tenant_id: Optional[str] = None,
    regenerate_hashes: bool = True,
    **kwargs
) -> Dict[str, Any]:
    """
    Import artifacts from specified format.

    Args:
        input_path: Input file path
        format: Import format (RDF, NEO4J, JSONLD)
        output_dir: Output directory for artifacts
        tenant_id: Optional tenant ID
        regenerate_hashes: Recalculate integrity hashes (default: True)
        **kwargs: Format-specific options

    Returns:
        Import statistics dict
    """
    if format == ImportFormat.RDF:
        return import_from_rdf(
            input_path, output_dir,
            tenant_id=tenant_id,
            regenerate_hashes=regenerate_hashes,
            format=kwargs.get("rdf_format", "turtle")
        )
    elif format == ImportFormat.JSONLD:
        return import_from_jsonld(
            input_path, output_dir,
            tenant_id=tenant_id,
            regenerate_hashes=regenerate_hashes
        )
    elif format == ImportFormat.NEO4J:
        raise ValueError(
            "Neo4j import requires connection parameters. "
            "Use import_from_neo4j() directly."
        )
    else:
        raise ValueError(f"Unsupported import format: {format}")


def check_dependencies(format: ImportFormat) -> Tuple[bool, str]:
    """Check if dependencies for import format are available."""
    if not FACTORY_AVAILABLE:
        return False, "tools.factory not available"

    if format == ImportFormat.RDF:
        if not RDFLIB_AVAILABLE:
            return False, "rdflib not available. Install with: pip install rdflib"
    elif format == ImportFormat.NEO4J:
        if not NEO4J_AVAILABLE:
            return False, "neo4j driver not available. Install with: pip install neo4j"

    return True, ""


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Novyx Core Import - Phase 11"
    )
    parser.add_argument(
        "--input", "-i",
        required=True,
        help="Input file path"
    )
    parser.add_argument(
        "--format", "-f",
        choices=["rdf", "jsonld"],
        default="jsonld",
        help="Import format (default: jsonld)"
    )
    parser.add_argument(
        "--output", "-o",
        default="memory",
        help="Output directory (default: memory)"
    )
    parser.add_argument(
        "--tenant",
        help="Tenant ID for imported artifacts"
    )
    parser.add_argument(
        "--no-regenerate-hashes",
        action="store_true",
        help="Preserve original integrity hashes"
    )
    parser.add_argument(
        "--rdf-format",
        choices=["turtle", "xml", "n3", "nt"],
        default="turtle",
        help="RDF serialization format (default: turtle)"
    )

    args = parser.parse_args()

    # Map format string to enum
    format_map = {
        "rdf": ImportFormat.RDF,
        "jsonld": ImportFormat.JSONLD,
    }
    import_format = format_map[args.format]

    # Check dependencies
    available, msg = check_dependencies(import_format)
    if not available:
        print(f"Error: {msg}", file=sys.stderr)
        sys.exit(1)

    print(f"Importing from {args.input} ({args.format.upper()})...")

    stats = import_artifacts(
        Path(args.input),
        import_format,
        Path(args.output),
        tenant_id=args.tenant,
        regenerate_hashes=not args.no_regenerate_hashes,
        rdf_format=args.rdf_format
    )

    print(f"\nImport Complete:")
    print(f"  Total items: {stats.get('total_artifacts', stats.get('total_subjects', 0))}")
    print(f"  Imported: {stats['imported']}")
    print(f"  Failed: {stats['failed']}")
    print(f"  Skipped: {stats['skipped']}")
    print(f"  Output: {stats['output_dir']}")
