#!/usr/bin/env python3
"""
Novyx Entity Generator - AI Visibility Infrastructure
======================================================
Bridges local knowledge graphs with global AI search ecosystem.

This tool enables:
1. External authority linking (sameAs) for AI agent discovery
2. Machine-readable entity generation (Organization, Person, Product)
3. AI Passport creation for citation and trust signals
4. Integrity validation for external links

The goal: Make Novyx Core discoverable and citable by AI search agents in 2026.

Usage:
    python tools/entity_generator.py --validate
    python tools/entity_generator.py --generate person --name "Blake Heron"
    python tools/entity_generator.py --add-authority <artifact_id> --url <external_url>
"""

import json
import sys
from tools.util import calculate_hash
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from datetime import datetime
import uuid
import subprocess


class EntityGenerator:
    """
    Generate and manage machine-readable entities for AI visibility.

    Creates schema.org-compliant JSON-LD entities that AI agents can:
    - Discover via search
    - Cite as authoritative sources
    - Verify through cryptographic integrity
    - Link to external identity providers (LinkedIn, GitHub, ORCID)
    """

    def __init__(self, root_dir: Path):
        self.root_dir = root_dir
        self.schema_path = root_dir / "CORE_SCHEMA.jsonld"
        self.org_path = root_dir / "organization.jsonld"
        self.memory_path = root_dir / "memory"

    def validate_organization(self) -> Tuple[bool, List[str]]:
        """
        Validate organization.jsonld for AI discoverability.

        Checks:
        - Required schema.org fields present
        - sameAs links are valid URLs
        - External authorities are reachable
        - JSON-LD structure is valid
        """
        errors = []

        if not self.org_path.exists():
            errors.append("organization.jsonld not found")
            return False, errors

        try:
            with open(self.org_path, 'r') as f:
                data = json.load(f)
        except json.JSONDecodeError as e:
            errors.append(f"Invalid JSON: {e}")
            return False, errors

        # Validate required schema.org fields
        required_fields = ['@context', '@type', 'name', 'description', 'url']
        for field in required_fields:
            if field not in data:
                errors.append(f"Missing required field: {field}")

        # Validate @type
        if data.get('@type') != 'Organization':
            errors.append(
                f"Expected @type='Organization', got '{data.get('@type')}'")

        # Validate sameAs links
        if 'sameAs' in data:
            if not isinstance(data['sameAs'], list):
                errors.append("sameAs must be an array")
            else:
                for url in data['sameAs']:
                    if not url.startswith('http'):
                        errors.append(f"Invalid sameAs URL: {url}")
        else:
            errors.append(
                "Missing sameAs field for external authority linking")

        # Validate founder
        if 'founder' in data:
            founder = data['founder']
            if '@type' not in founder or founder['@type'] != 'Person':
                errors.append("Founder must have @type='Person'")
            if 'sameAs' not in founder:
                errors.append("Founder missing sameAs for external identity")

        # Check for AI visibility markers
        if 'novyx:aiVisibility' in data:
            visibility = data['novyx:aiVisibility']
            if not visibility.get('citeable'):
                errors.append("AI visibility set but not citeable")
            if not visibility.get('authoritative'):
                errors.append("AI visibility set but not authoritative")

        return len(errors) == 0, errors

    def validate_schema(self) -> Tuple[bool, List[str]]:
        """
        Validate CORE_SCHEMA.jsonld includes external authority support.
        """
        errors = []

        if not self.schema_path.exists():
            errors.append("CORE_SCHEMA.jsonld not found")
            return False, errors

        try:
            with open(self.schema_path, 'r') as f:
                data = json.load(f)
        except json.JSONDecodeError as e:
            errors.append(f"Invalid JSON: {e}")
            return False, errors

        # Check for sameAs definition
        context = data.get('@context', {})
        if 'sameAs' not in context:
            errors.append(
                "CORE_SCHEMA missing 'sameAs' definition for external linking")

        # Check for external authority
        if 'External_Authority' not in context:
            errors.append(
                "CORE_SCHEMA missing 'External_Authority' definition")

        # Check schema version
        meta = data.get('meta', {})
        if not meta.get('ai_visibility'):
            errors.append("CORE_SCHEMA.meta missing 'ai_visibility' flag")

        return len(errors) == 0, errors

    def add_external_authority(self, artifact_path: Path,
                               external_urls: List[str]) -> bool:
        """
        Add sameAs external authority links to an artifact.

        Args:
            artifact_path: Path to artifact JSON-LD file
            external_urls: List of external URLs (GitHub, LinkedIn, etc.)
        """
        if not artifact_path.exists():
            print(f"❌ Artifact not found: {artifact_path}")
            return False

        try:
            with open(artifact_path, 'r') as f:
                data = json.load(f)
        except json.JSONDecodeError as e:
            print(f"❌ Invalid JSON: {e}")
            return False

        # Add sameAs array
        if 'sameAs' not in data:
            data['sameAs'] = []

        # Add new URLs (avoid duplicates)
        for url in external_urls:
            if url not in data['sameAs']:
                data['sameAs'].append(url)

        # Recalculate integrity hash (Sentinel-compatible)
        hash_field = self._detect_hash_field(data, artifact_path)
        if not hash_field:
            return False
        data[hash_field] = self._calculate_hash(data, hash_field)

        # Save
        try:
            with open(artifact_path, 'w') as f:
                json.dump(data, f, indent=2)
            print(f"✅ Added {len(external_urls)} external authority link(s)")
            return True
        except Exception as e:
            print(f"❌ Error saving file: {e}")
            return False

    def _calculate_hash(self, data: dict, hash_field: str) -> str:
        """Calculate SHA-256 hash matching Sentinel's method."""
        return calculate_hash(data, exclude_fields={hash_field})

    def _detect_hash_field(self, data: dict, artifact_path: Path) -> Optional[str]:
        """Determine which integrity hash field to use based on schema version."""
        has_v11 = 'novyx:integrityHash' in data
        has_v10 = 'Integrity_Hash' in data

        if has_v11 and has_v10:
            print(
                f"⚠️  Ambiguous schema for {artifact_path}: both hash fields present. No changes applied."
            )
            return None
        if has_v11:
            return 'novyx:integrityHash'
        if has_v10:
            return 'Integrity_Hash'

        print(
            f"⚠️  Ambiguous schema for {artifact_path}: no hash field present. No changes applied."
        )
        return None

    def generate_person_entity(self, name: str,
                               role: str = "Contributor",
                               github: Optional[str] = None,
                               linkedin: Optional[str] = None) -> dict:
        """
        Generate a schema.org Person entity for AI citation.

        Args:
            name: Person's name
            role: Their role (Founder, Contributor, etc.)
            github: GitHub profile URL
            linkedin: LinkedIn profile URL

        Returns:
            JSON-LD Person entity
        """
        person = {
            "@context": "https://schema.org/",
            "@type": "Person",
            "@id": f"https://novyx.ai/person/{name.lower().replace(' ', '-')}",
            "name": name,
            "jobTitle": role,
            "affiliation": {
                "@type": "Organization",
                "@id": "https://novyx.ai/organization",
                "name": "Novyx Labs"
            },
            "worksFor": {
                "@type": "Organization",
                "@id": "https://novyx.ai/organization",
                "name": "Novyx Labs"
            }
        }

        # Add external identities
        same_as = []
        if github:
            same_as.append(github)
        if linkedin:
            same_as.append(linkedin)

        if same_as:
            person['sameAs'] = same_as

        return person

    def generate_software_entity(self, name: str, description: str,
                                 version: str = "1.0.0") -> dict:
        """
        Generate a schema.org SoftwareApplication entity.

        Args:
            name: Software name
            description: Software description
            version: Version number

        Returns:
            JSON-LD SoftwareApplication entity
        """
        software = {
            "@context": "https://schema.org/",
            "@type": "SoftwareApplication",
            "@id": f"https://novyx.ai/software/{name.lower().replace(' ', '-')}",
            "name": name,
            "description": description,
            "version": version,
            "applicationCategory": "AI/ML Tool",
            "operatingSystem": ["Linux", "macOS", "Windows"],
            "author": {
                "@type": "Organization",
                "@id": "https://novyx.ai/organization",
                "name": "Novyx Labs"
            },
            "offers": {
                "@type": "Offer",
                "price": "0",
                "priceCurrency": "USD",
                "description": "Open source software"
            }
        }

        return software

    def run_sentinel_validation(self) -> bool:
        """
        Run Sentinel to ensure external links don't break internal integrity.
        """
        print("🛡️  Running Sentinel integrity check...")

        result = subprocess.run(
            ['python3', str(self.root_dir / 'tools' / 'sentinel.py')],
            cwd=self.root_dir,
            capture_output=True,
            text=True
        )

        if result.returncode == 0:
            print("✅ Sentinel validation passed")
            return True
        else:
            print("❌ Sentinel validation failed:")
            print(result.stdout)
            return False

    def generate_ai_visibility_report(self) -> Dict:
        """
        Generate a report on AI visibility and discoverability status.
        """
        report = {
            "timestamp": datetime.utcnow().isoformat() + "Z",
            "ai_visibility_status": {},
            "external_authorities": [],
            "trust_signals": [],
            "discoverability_score": 0
        }

        # Check organization.jsonld
        org_valid, org_errors = self.validate_organization()
        report['ai_visibility_status']['organization'] = {
            "valid": org_valid,
            "errors": org_errors
        }

        # Check schema.jsonld
        schema_valid, schema_errors = self.validate_schema()
        report['ai_visibility_status']['schema'] = {
            "valid": schema_valid,
            "errors": schema_errors
        }

        # Extract external authorities
        if self.org_path.exists():
            with open(self.org_path, 'r') as f:
                org_data = json.load(f)
                report['external_authorities'] = org_data.get('sameAs', [])

                # Extract trust signals
                if 'novyx:aiVisibility' in org_data:
                    report['trust_signals'] = org_data['novyx:aiVisibility'].get(
                        'trustSignals', [])

        # Calculate discoverability score
        score = 0
        if org_valid:
            score += 40
        if schema_valid:
            score += 20
        if len(report['external_authorities']) >= 2:
            score += 20
        if len(report['trust_signals']) >= 3:
            score += 20

        report['discoverability_score'] = score
        report['rating'] = self._get_rating(score)

        return report

    def _get_rating(self, score: int) -> str:
        """Get human-readable rating from score."""
        if score >= 90:
            return "Excellent - Highly discoverable by AI agents"
        elif score >= 70:
            return "Good - Discoverable with strong trust signals"
        elif score >= 50:
            return "Fair - Basic discoverability established"
        else:
            return "Poor - Limited AI visibility"


def main():
    """Entry point for entity generator."""
    import argparse

    parser = argparse.ArgumentParser(
        description='Novyx Entity Generator - AI Visibility Infrastructure',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python tools/entity_generator.py --validate
  python tools/entity_generator.py --report
  python tools/entity_generator.py --generate person --name "Jane Doe" --github https://github.com/janedoe
  python tools/entity_generator.py --add-authority memory/health/log_001.jsonld --url https://linkedin.com/in/user
        """
    )

    parser.add_argument('--validate', action='store_true',
                        help='Validate AI visibility infrastructure')
    parser.add_argument('--report', action='store_true',
                        help='Generate AI discoverability report')
    parser.add_argument('--generate', type=str, choices=['person', 'software'],
                        help='Generate entity type')
    parser.add_argument('--name', type=str, help='Entity name')
    parser.add_argument('--role', type=str,
                        default='Contributor', help='Person role')
    parser.add_argument('--description', type=str, help='Software description')
    parser.add_argument('--github', type=str, help='GitHub URL')
    parser.add_argument('--linkedin', type=str, help='LinkedIn URL')
    parser.add_argument('--add-authority', type=str,
                        help='Add external authority to artifact')
    parser.add_argument('--url', type=str, action='append',
                        help='External URL (can specify multiple)')

    args = parser.parse_args()

    # Determine root directory
    current_dir = Path.cwd()
    if current_dir.name == 'tools':
        root_dir = current_dir.parent
    else:
        root_dir = current_dir

    print("=" * 70)
    print("🌐 NOVYX ENTITY GENERATOR - AI Visibility Infrastructure")
    print("=" * 70)

    generator = EntityGenerator(root_dir)

    # Validate
    if args.validate:
        print("\n🔍 Validating AI visibility infrastructure...\n")

        # Validate organization
        org_valid, org_errors = generator.validate_organization()
        print("📋 Organization Entity (organization.jsonld):")
        if org_valid:
            print("   ✅ Valid - AI agents can discover and cite")
        else:
            print("   ❌ Invalid:")
            for error in org_errors:
                print(f"      • {error}")

        # Validate schema
        schema_valid, schema_errors = generator.validate_schema()
        print("\n📐 Core Schema (CORE_SCHEMA.jsonld):")
        if schema_valid:
            print("   ✅ Valid - External authority linking enabled")
        else:
            print("   ❌ Invalid:")
            for error in schema_errors:
                print(f"      • {error}")

        # Run Sentinel
        print()
        sentinel_valid = generator.run_sentinel_validation()

        # Overall status
        print("\n" + "=" * 70)
        if org_valid and schema_valid and sentinel_valid:
            print("✅ AI VISIBILITY: OPERATIONAL")
            print("   Your system is discoverable and citable by AI search agents")
        else:
            print("⚠️  AI VISIBILITY: INCOMPLETE")
            print("   Fix errors above to enable full AI discoverability")
        print("=" * 70)

        sys.exit(0 if (org_valid and schema_valid and sentinel_valid) else 1)

    # Generate report
    if args.report:
        print("\n📊 Generating AI discoverability report...\n")
        report = generator.generate_ai_visibility_report()

        print(f"Timestamp: {report['timestamp']}")
        print(
            f"\n🎯 Discoverability Score: {report['discoverability_score']}/100")
        print(f"   Rating: {report['rating']}")

        print(
            f"\n🔗 External Authorities ({len(report['external_authorities'])}):")
        for url in report['external_authorities']:
            print(f"   • {url}")

        print(f"\n🛡️  Trust Signals ({len(report['trust_signals'])}):")
        for signal in report['trust_signals']:
            print(f"   • {signal}")

        print(f"\n✅ Component Status:")
        for component, status in report['ai_visibility_status'].items():
            valid = status['valid']
            symbol = "✅" if valid else "❌"
            print(f"   {symbol} {component.capitalize()}")
            if not valid and status['errors']:
                for error in status['errors']:
                    print(f"      ⚠️  {error}")

        sys.exit(0)

    # Generate entity
    if args.generate:
        if args.generate == 'person':
            if not args.name:
                print("❌ --name required for person generation")
                sys.exit(1)

            person = generator.generate_person_entity(
                args.name,
                role=args.role,
                github=args.github,
                linkedin=args.linkedin
            )

            print(f"\n✅ Generated Person Entity: {args.name}")
            print(json.dumps(person, indent=2))

        elif args.generate == 'software':
            if not args.name or not args.description:
                print("❌ --name and --description required for software generation")
                sys.exit(1)

            software = generator.generate_software_entity(
                args.name,
                args.description
            )

            print(f"\n✅ Generated Software Entity: {args.name}")
            print(json.dumps(software, indent=2))

        sys.exit(0)

    # Add external authority
    if args.add_authority:
        if not args.url:
            print("❌ --url required when adding external authority")
            sys.exit(1)

        artifact_path = Path(args.add_authority)
        if not artifact_path.is_absolute():
            artifact_path = root_dir / artifact_path

        success = generator.add_external_authority(artifact_path, args.url)

        if success:
            # Validate with Sentinel
            generator.run_sentinel_validation()

        sys.exit(0 if success else 1)

    # No action specified
    parser.print_help()
    sys.exit(1)


if __name__ == '__main__':
    main()


