"""
Beispiel für Social Media Platform mit Pagination-Tests.

Erstellt drei Inventories in einer Hierarchie:
1. Users (200 Items)
2. Posts (30000+ Items, 100-500 pro User)
3. Hashtags (1000 Items, 5-20 pro Post)

Relationen:
- User -> Posts (1:n)
- Posts <-> Hashtags (n:m)
"""

from __future__ import annotations

import random
import uuid
from datetime import datetime, timedelta

from questra_authentication import QuestraAuthentication

from questra_data import (
    BoolProperty,
    DateTimeProperty,
    IntProperty,
    InventoryRelation,
    QuestraData,
    RelationType,
    StringProperty,
)


def create_inventories(client: QuestraData) -> None:
    """Erstellt die drei Inventories für die Social Media Platform."""
    print("=" * 80)
    print("Inventories erstellen")
    print("=" * 80)

    # Namespace erstellen
    ns_result = client.create_namespace(
        name="SocialMedia", description="Social Media Platform für Pagination-Tests"
    )
    print(f"Namespace: {ns_result.name} (existierte: {ns_result.existed})")

    # 1. Users Inventory (oberste Ebene)
    user_properties = [
        StringProperty(
            propertyName="username",
            maxLength=50,
            isCaseSensitive=False,
            isRequired=True,
            isUnique=True,
            description="Eindeutiger Benutzername",
        ),
        StringProperty(
            propertyName="email",
            maxLength=200,
            isRequired=True,
            isUnique=True,
            description="E-Mail-Adresse",
        ),
        StringProperty(
            propertyName="display_name",
            maxLength=100,
            isRequired=True,
            description="Anzeigename",
        ),
        StringProperty(
            propertyName="bio",
            maxLength=500,
            isRequired=False,
            description="Biographie",
        ),
        DateTimeProperty(
            propertyName="created_at",
            isRequired=True,
            description="Registrierungsdatum",
        ),
        IntProperty(
            propertyName="follower_count",
            isRequired=False,
            description="Anzahl Follower",
        ),
        BoolProperty(
            propertyName="is_verified",
            isRequired=False,
            description="Verifizierter Account",
        ),
    ]

    users_result = client.create_inventory(
        name="Users",
        namespace_name="SocialMedia",
        properties=user_properties,
        description="Benutzer der Social Media Platform",
    )
    print(f"Inventory Users: {users_result.name} (existierte: {users_result.existed})")

    # 2. Posts Inventory (mittlere Ebene, mit Relation zu Users)
    post_properties = [
        StringProperty(
            propertyName="content",
            maxLength=1000,
            isRequired=True,
            description="Post-Inhalt",
        ),
        StringProperty(
            propertyName="title",
            maxLength=200,
            isRequired=False,
            description="Post-Titel (optional)",
        ),
        DateTimeProperty(
            propertyName="created_at",
            isRequired=True,
            description="Erstellungszeitpunkt",
        ),
        IntProperty(
            propertyName="like_count", isRequired=False, description="Anzahl Likes"
        ),
        IntProperty(
            propertyName="comment_count",
            isRequired=False,
            description="Anzahl Kommentare",
        ),
        BoolProperty(
            propertyName="is_public",
            isRequired=False,
            description="Öffentlich sichtbar",
        ),
    ]

    post_relations = [
        InventoryRelation(
            propertyName="author",
            relationType=RelationType.ONE_TO_MANY,
            parentInventoryName="Users",
            parentPropertyName="posts",
            parentNamespaceName="SocialMedia",  # Explizit angeben!
        )
    ]

    posts_result = client.create_inventory(
        name="Posts",
        namespace_name="SocialMedia",
        properties=post_properties,
        relations=post_relations,
        description="Beiträge der Benutzer",
    )
    print(f"Inventory Posts: {posts_result.name} (existierte: {posts_result.existed})")

    # 3. Hashtags Inventory (unterste Ebene, n:m Relation zu Posts)
    hashtag_properties = [
        StringProperty(
            propertyName="tag",
            maxLength=100,
            isCaseSensitive=False,
            isRequired=True,
            isUnique=True,
            description="Hashtag (ohne #)",
        ),
        IntProperty(
            propertyName="usage_count",
            isRequired=False,
            description="Anzahl Verwendungen",
        ),
        DateTimeProperty(
            propertyName="created_at", isRequired=True, description="Erstellungsdatum"
        ),
        BoolProperty(
            propertyName="is_trending", isRequired=False, description="Trending Hashtag"
        ),
    ]

    hashtag_relations = [
        InventoryRelation(
            propertyName="posts",
            relationType=RelationType.MANY_TO_MANY,
            parentInventoryName="Posts",
            parentPropertyName="hashtags",
            parentNamespaceName="SocialMedia",  # Explizit angeben!
        )
    ]

    hashtags_result = client.create_inventory(
        name="Hashtags",
        namespace_name="SocialMedia",
        properties=hashtag_properties,
        relations=hashtag_relations,
        description="Hashtags für Posts",
    )
    print(
        f"Inventory Hashtags: {hashtags_result.name} (existierte: {hashtags_result.existed})"
    )
    print()


def generate_users(
    client: QuestraData, count: int = 200
) -> list[dict[str, str | int | bool]]:
    """Generiert User-Daten."""
    print("=" * 80)
    print(f"Generiere {count} User")
    print("=" * 80)

    # Realistische Namen für Testdaten
    first_names = [
        "Max",
        "Anna",
        "Leon",
        "Maria",
        "Paul",
        "Sophie",
        "Felix",
        "Laura",
        "Lukas",
        "Emma",
        "Jonas",
        "Mia",
        "Elias",
        "Hannah",
        "Noah",
        "Lena",
        "Ben",
        "Sarah",
        "Tim",
        "Julia",
        "Finn",
        "Lisa",
        "Jan",
        "Nina",
        "Tom",
        "Clara",
        "Nico",
        "Lea",
        "David",
        "Marie",
    ]
    last_names = [
        "Müller",
        "Schmidt",
        "Schneider",
        "Fischer",
        "Weber",
        "Meyer",
        "Wagner",
        "Becker",
        "Schulz",
        "Hoffmann",
        "Koch",
        "Bauer",
        "Richter",
        "Klein",
        "Wolf",
        "Schröder",
        "Neumann",
        "Schwarz",
        "Zimmermann",
        "Braun",
    ]

    users = []
    start_date = datetime.now() - timedelta(days=730)  # 2 Jahre zurück

    for i in range(count):
        first = random.choice(first_names)
        last = random.choice(last_names)
        username = f"{first.lower()}.{last.lower()}{i}"

        user = {
            "username": username,
            "email": f"{username}@example.com",
            "display_name": f"{first} {last}",
            "bio": f"Hallo! Ich bin {first} und teile meine Gedanken hier.",
            "created_at": (
                start_date + timedelta(days=random.randint(0, 730))
            ).isoformat(),
            "follower_count": random.randint(10, 5000),
            "is_verified": random.random() < 0.1,  # 10% verifiziert
        }
        users.append(user)

    # Batch-Insert (je 100 Items)
    batch_size = 100
    created_user_ids = []

    for i in range(0, len(users), batch_size):
        batch = users[i : i + batch_size]
        result = client.create_items(
            inventory_name="Users", namespace_name="SocialMedia", items=batch
        )
        created_user_ids.extend(result)
        print(f"  {len(created_user_ids)}/{count} Users erstellt...")

    # Kombiniere Original-Daten mit IDs
    # create_items() gibt nur _id, _rowVersion, _existed zurück
    for original_user, created_user in zip(users, created_user_ids):
        original_user["_id"] = created_user["_id"]
        original_user["_rowVersion"] = created_user["_rowVersion"]
        original_user["_existed"] = created_user.get("_existed", False)

    print(f"✓ {len(users)} Users erstellt")
    print()
    return users  # Gebe Original-Daten mit IDs zurück


def generate_posts(
    client: QuestraData,
    users: list[dict[str, str | int | bool]],
    min_posts: int = 100,
    max_posts: int = 500,
) -> list[dict[str, str | int | bool]]:
    """Generiert Posts für alle User (100-500 pro User)."""
    print("=" * 80)
    print(f"Generiere Posts ({min_posts}-{max_posts} pro User)")
    print("=" * 80)

    # Beispiel-Content für Posts
    post_templates = [
        "Heute war ein toller Tag! {}",
        "Ich liebe {}, was ist euer Favorit?",
        "Neues Projekt gestartet: {}",
        "Gerade unterwegs nach {} 🚀",
        "Was denkt ihr über {}?",
        "Beste Entscheidung: {}!",
        "Endlich geschafft: {}",
        "Wer kennt es? {}",
        "Meinung zu {}: absolut empfehlenswert!",
        "Heute gelernt: {}",
    ]

    topics = [
        "Programmieren",
        "Reisen",
        "Fotografie",
        "Sport",
        "Kochen",
        "Musik",
        "Bücher",
        "Gaming",
        "Natur",
        "Technologie",
        "Kunst",
        "Fitness",
        "Meditation",
        "DIY",
        "Kaffee",
        "Abenteuer",
        "Filme",
        "Architektur",
    ]

    all_posts = []
    total_expected = len(users) * ((min_posts + max_posts) // 2)

    print(f"Erwartete Gesamtzahl: ~{total_expected:,} Posts")
    print()

    for idx, user in enumerate(users):
        user_id = user["_id"]
        num_posts = random.randint(min_posts, max_posts)
        user_posts = []

        # Zeitstempel für Posts (nach User-Erstellung)
        user_created = datetime.fromisoformat(user["created_at"].replace("Z", "+00:00"))

        for _ in range(num_posts):
            post_date = user_created + timedelta(
                days=random.randint(0, 365),
                hours=random.randint(0, 23),
                minutes=random.randint(0, 59),
            )

            template = random.choice(post_templates)
            topic = random.choice(topics)

            post = {
                "content": template.format(topic),
                "title": f"Gedanken zu {topic}" if random.random() < 0.3 else None,
                "created_at": post_date.isoformat(),
                "like_count": random.randint(0, 1000),
                "comment_count": random.randint(0, 200),
                "is_public": random.random() < 0.9,  # 90% öffentlich
                "_authorId": user_id,  # Relation: _{propertyName}Id
            }
            user_posts.append(post)

        # Batch-Insert für User-Posts
        batch_size = 100
        for i in range(0, len(user_posts), batch_size):
            batch = user_posts[i : i + batch_size]
            result = client.create_items(
                inventory_name="Posts", namespace_name="SocialMedia", items=batch
            )
            all_posts.extend(result)

        # Progress
        if (idx + 1) % 10 == 0 or idx == len(users) - 1:
            print(
                f"  {idx + 1}/{len(users)} Users verarbeitet | {len(all_posts):,} Posts erstellt..."
            )

    print(f"✓ {len(all_posts):,} Posts erstellt")
    print()
    return all_posts


def generate_hashtags(
    client: QuestraData, count: int = 1000
) -> list[dict[str, str | int | bool]]:
    """Generiert Hashtag-Daten."""
    print("=" * 80)
    print(f"Generiere {count} Hashtags")
    print("=" * 80)

    # Realistische Hashtags
    categories = {
        "tech": [
            "python",
            "javascript",
            "coding",
            "programming",
            "developer",
            "software",
            "ai",
            "machinelearning",
            "data",
            "cloud",
        ],
        "lifestyle": [
            "travel",
            "food",
            "fitness",
            "health",
            "fashion",
            "style",
            "photography",
            "art",
            "music",
            "design",
        ],
        "nature": [
            "nature",
            "outdoor",
            "hiking",
            "mountains",
            "forest",
            "beach",
            "sunset",
            "wildlife",
            "adventure",
            "explore",
        ],
        "social": [
            "instagood",
            "photooftheday",
            "love",
            "happy",
            "fun",
            "friends",
            "family",
            "life",
            "motivation",
            "inspiration",
        ],
        "hobby": [
            "gaming",
            "reading",
            "cooking",
            "diy",
            "crafts",
            "sports",
            "yoga",
            "meditation",
            "coffee",
            "books",
        ],
    }

    # Erweitere Hashtags mit Nummern und Kombinationen
    base_tags = []
    for tags in categories.values():
        base_tags.extend(tags)

    hashtags = []
    used_tags = set()  # Verhindere Duplikate

    for i in range(count):
        # Generiere Tag bis er unique ist
        tag = None
        attempts = 0
        while attempts < 100:  # Max 100 Versuche
            if i < len(base_tags):
                tag = base_tags[i]
            else:
                # Kombiniere zwei zufällige Tags oder füge Nummer hinzu
                if random.random() < 0.5:
                    tag = f"{random.choice(base_tags)}{random.randint(2025, 2030)}"
                else:
                    tag1 = random.choice(base_tags)
                    tag2 = random.choice(base_tags)
                    tag = f"{tag1}{tag2}"

            # Prüfe ob tag unique und nicht leer
            if tag and tag not in used_tags:
                used_tags.add(tag)
                break
            attempts += 1
            tag = None  # Reset für nächsten Versuch

        if tag is None or attempts >= 100:
            # Fallback: GUID garantiert Uniqueness
            tag = f"tag_{uuid.uuid4().hex[:8]}"
            used_tags.add(tag)

        # Validierung: Tag darf nicht None oder leer sein
        if not tag or not tag.strip():
            raise ValueError(f"Ungültiger Hashtag generiert: '{tag}' bei Index {i}")

        hashtag = {
            "tag": tag.strip(),
            "usage_count": random.randint(1, 10000),
            "created_at": (
                datetime.now() - timedelta(days=random.randint(1, 730))
            ).isoformat(),
            "is_trending": random.random() < 0.05,  # 5% trending
        }
        hashtags.append(hashtag)

    # Batch-Insert
    batch_size = 100
    created_hashtags = []

    for i in range(0, len(hashtags), batch_size):
        batch = hashtags[i : i + batch_size]
        result = client.create_items(
            inventory_name="Hashtags", namespace_name="SocialMedia", items=batch
        )
        created_hashtags.extend(result)
        print(f"  {len(created_hashtags)}/{count} Hashtags erstellt...")

    print(f"✓ {len(created_hashtags)} Hashtags erstellt")
    print()
    return created_hashtags


def link_posts_to_hashtags(
    client: QuestraData,
    posts: list[dict[str, str | int | bool]],
    hashtags: list[dict[str, str | int | bool]],
    min_tags: int = 5,
    max_tags: int = 20,
) -> None:
    """
    Verknüpft Posts mit Hashtags (n:m Relation).

    Bei n:m-Relationen muss die Verknüpfung von der Child-Seite (Hashtags) aus erfolgen,
    da nur dort die Mutation existiert (socialMedia__updateHashtags).
    """
    print("=" * 80)
    print(f"Verknüpfe Posts mit Hashtags ({min_tags}-{max_tags} pro Post)")
    print("=" * 80)

    if not hashtags:
        print("⚠ Keine Hashtags vorhanden - überspringe Verknüpfung")
        return

    total_links = 0

    # Erstelle Post->Hashtag Zuordnung
    post_hashtags = {}  # post_id -> [hashtag_ids]
    for post in posts:
        num_tags = min(random.randint(min_tags, max_tags), len(hashtags))
        selected_tags = random.sample(hashtags, num_tags)
        post_hashtags[post["_id"]] = [h["_id"] for h in selected_tags]

    # Invertiere zu Hashtag->Posts Zuordnung
    hashtag_posts = {}  # hashtag_id -> [post_ids]
    for post_id, tag_ids in post_hashtags.items():
        for tag_id in tag_ids:
            if tag_id not in hashtag_posts:
                hashtag_posts[tag_id] = []
            hashtag_posts[tag_id].append(post_id)
            total_links += 1

    # Update Hashtags mit Post-Relationen (von Child-Seite)
    updates = []
    for hashtag in hashtags:
        hashtag_id = hashtag["_id"]
        if hashtag_id in hashtag_posts:
            update = {
                "_id": hashtag_id,
                "_rowVersion": hashtag["_rowVersion"],
                "posts": hashtag_posts[hashtag_id],  # Direkt IDs als Array
            }
            updates.append(update)

    # Batch-Update
    if updates:
        batch_size = 100
        for batch_start in range(0, len(updates), batch_size):
            batch_end = min(batch_start + batch_size, len(updates))
            batch = updates[batch_start:batch_end]

            try:
                client.lowlevel.inventory.update(
                    inventory_name="Hashtags", namespace_name="SocialMedia", items=batch
                )
                print(f"  {batch_end}/{len(updates)} Hashtags aktualisiert...")
            except Exception as e:
                print(
                    f"  Warnung: Batch {batch_start}-{batch_end} konnte nicht aktualisiert werden: {e}"
                )

    print(
        f"✓ {len(updates)} Hashtags mit Posts verknüpft (~{total_links:,} Relationen)"
    )
    print()


def print_statistics(client: QuestraData) -> None:
    """Gibt Statistiken über die erstellten Daten aus."""
    print("\n")
    print("=" * 80)
    print("STATISTIKEN")
    print("=" * 80)

    # Count Users (High-Level API limit)
    users = client.list_items(
        inventory_name="Users",
        namespace_name="SocialMedia",
        properties=["_id"],
        limit=1000,
    )
    print(f"  Users:    {len(users):,}")

    # Count Posts (High-Level API limit, könnte mehr sein)
    # Hinweis: list_items ist auf 'first' limitiert, für große Mengen cursor-basiert paginieren
    posts = client.list_items(
        inventory_name="Posts",
        namespace_name="SocialMedia",
        properties=["_id"],
        limit=1000,
    )
    print(f"  Posts:    {len(posts):,} (möglicherweise mehr vorhanden)")

    # Count Hashtags
    hashtags = client.list_items(
        inventory_name="Hashtags",
        namespace_name="SocialMedia",
        properties=["_id"],
        limit=1000,
    )
    print(f"  Hashtags: {len(hashtags):,}")

    # Durchschnittliche Posts pro User (basierend auf geladenen Daten)
    avg_posts = len(posts) / len(users) if users else 0
    print(f"\n  Ø Posts pro User: {avg_posts:.1f} (geschätzt)")

    print("=" * 80)


def main():
    """Hauptfunktion: Erstellt Social Media Platform Datenmodell."""
    print("\n")
    print("=" * 80)
    print("SOCIAL MEDIA PLATFORM - PAGINATION TEST DATENMODELL")
    print("=" * 80)
    print()

    # ========================================================================
    # KONFIGURATION - Passe hier die Datenmengen an
    # ========================================================================
    NUM_USERS = 200  # Anzahl Users (für Produktion: 200)
    NUM_HASHTAGS = 1000  # Anzahl Hashtags (für Produktion: 1000)
    MIN_POSTS_PER_USER = 50  # Min. Posts pro User (für Produktion: 50)
    MAX_POSTS_PER_USER = 150  # Max. Posts pro User (für Produktion: 150)
    MIN_TAGS_PER_POST = 5  # Min. Hashtags pro Post (für Produktion: 5)
    MAX_TAGS_PER_POST = 20  # Max. Hashtags pro Post (für Produktion: 20)
    # ========================================================================

    # Client initialisieren
    auth_client = QuestraAuthentication(
        url="https://authentik.dev.questra.s2o.dev",
        interactive=True,
    )

    client = QuestraData(
        graphql_url="https://dev.questra.s2o.dev/dynamic-objects/graphql",
        auth_client=auth_client,
    )

    print(f"✓ Client authentifiziert: {client.is_authenticated()}")
    print()

    # Inventories erstellen
    create_inventories(client)

    # Daten generieren
    users = generate_users(client, count=NUM_USERS)
    posts = generate_posts(
        client, users, min_posts=MIN_POSTS_PER_USER, max_posts=MAX_POSTS_PER_USER
    )
    hashtags = generate_hashtags(client, count=NUM_HASHTAGS)

    # Relationen erstellen
    link_posts_to_hashtags(
        client, posts, hashtags, min_tags=MIN_TAGS_PER_POST, max_tags=MAX_TAGS_PER_POST
    )

    # Statistiken
    print_statistics(client)

    print("\n")
    print("=" * 80)
    print("✓ FERTIG - Social Media Platform Datenmodell erstellt!")
    print("=" * 80)
    print()
    print("Du kannst jetzt folgende Pagination-Szenarien testen:")
    print("  1. Lade 100 Users, für jeden User die ersten 100 Posts")
    print("  2. Lade 100 Posts, für jeden Post die zugehörigen Hashtags")
    print("  3. Verschachtelte Pagination über alle 3 Ebenen")
    print()
    print("Beispiel für Pagination-Tests:")
    print()
    print("  # Cursor-basierte Pagination über Posts")
    print("  first_page = client.lowlevel.inventory.list(")
    print("      inventory_name='Posts',")
    print("      namespace_name='SocialMedia',")
    print("      properties=['_id', 'content', 'author._id'],")
    print("      first=100")
    print("  )")
    print("  ")
    print("  # Nächste Seite mit Cursor")
    print("  cursor = first_page['pageInfo']['endCursor']")
    print("  if first_page['pageInfo']['hasNextPage']:")
    print("      next_page = client.lowlevel.inventory.list(")
    print("          inventory_name='Posts',")
    print("          namespace_name='SocialMedia',")
    print("          properties=['_id', 'content'],")
    print("          first=100,")
    print("          after=cursor")
    print("      )")
    print()


if __name__ == "__main__":
    main()
