"""Demo: conscribe + Pydantic BaseModel + Generic[T] compatibility.

Problem: Pydantic Generic specialization (Base[str]) creates new classes,
which triggers both metaclass __new__ and __init_subclass__, causing
unwanted registrations like 'base[str]', 'base[none_type]'.

This demo tests the recommended strategy:
  Custom __init_subclass__ that skips Generic specialization classes.
"""

from __future__ import annotations

from typing import Any, Generic, Protocol, TypeVar, runtime_checkable

from pydantic import BaseModel, Field
from conscribe import create_registrar


@runtime_checkable
class EventProtocol(Protocol):
    event_id: str


T_Result = TypeVar('T_Result')


def separator(title: str) -> None:
    print(f'\n{"=" * 60}')
    print(title)
    print('=' * 60)


# ============================================================
# Show the problem: bridge + Generic pollutes registry
# ============================================================

separator('Problem: bridge + Generic pollutes registry')

RegProblem = create_registrar(
    'problem', EventProtocol,
    discriminator_field='event_type',
    strip_suffixes=['Event'],
)
BridgeProblem = RegProblem.bridge(BaseModel)


class ProblemBase(BridgeProblem, Generic[T_Result]):
    __abstract__ = True
    event_id: str = Field(default='x')


class ProblemNavEvent(ProblemBase[str]):
    url: str


class ProblemCrashEvent(ProblemBase[None]):
    target_id: str


print(f'Keys: {RegProblem.keys()}')
bracket_keys = [k for k in RegProblem.keys() if '[' in k]
print(f'Unwanted bracket keys: {bracket_keys}')
assert len(bracket_keys) > 0, 'Expected bracket keys from Generic specialization'
print('Confirmed: Generic specialization pollutes the registry.\n')


# ============================================================
# Solution: custom __init_subclass__
# Uses real class names (no numeric suffixes) so strip works.
# ============================================================

separator('Solution: custom __init_subclass__ + Generic')

EventReg = create_registrar(
    'event', EventProtocol,
    discriminator_field='event_type',
    strip_suffixes=['Event'],
)


class BaseEvent(BaseModel, Generic[T_Result]):
    """Base event class with auto-registration via conscribe.

    Generic specialization classes (e.g. BaseEvent[str]) are skipped.
    Abstract subclasses (with __abstract__ = True) are skipped.
    """

    event_id: str = Field(default='test-id')
    consumed: bool = False

    def __init_subclass__(cls, **kwargs: Any) -> None:
        super().__init_subclass__(**kwargs)
        # Skip Pydantic Generic specialization (name contains '[')
        if '[' in cls.__name__:
            return
        # Skip abstract bases
        if cls.__dict__.get('__abstract__', False):
            return
        # Auto-register concrete subclass
        EventReg._registry.add(
            EventReg._key_transform(cls.__name__),
            cls,
            protocol_check=False,
        )

    def consume(self) -> None:
        self.consumed = True


# --- Concrete event classes (auto-registered) ---

class NavigateToUrlEvent(BaseEvent[str]):
    """Navigate to a URL. Result type: str (final URL after redirects)."""
    url: str


class CrashEvent(BaseEvent[None]):
    """Browser tab crashed."""
    target_id: str


class ScreenshotEvent(BaseEvent[str]):
    """Take a screenshot. Result type: str (base64-encoded image)."""
    full_page: bool = False


# --- Abstract intermediate base (NOT registered) ---

class LifecycleEvent(BaseEvent[None]):
    """Abstract base for lifecycle events."""
    __abstract__ = True


class SessionStartEvent(LifecycleEvent):
    """Session started."""
    session_id: str


class SessionEndEvent(LifecycleEvent):
    """Session ended."""
    session_id: str
    reason: str = 'normal'


# ============================================================
# Verify: Registry
# ============================================================

separator('Verify: Registry')

keys = EventReg.keys()
print(f'Registered keys: {keys}')

expected = {
    'navigate_to_url',
    'crash',
    'screenshot',
    'session_start',
    'session_end',
}
actual = set(keys)
assert actual == expected, f'Expected {expected}, got {actual}'
print(f'Key count: {len(keys)} (correct)')

# No bracket keys
bracket_keys = [k for k in keys if '[' in k]
assert len(bracket_keys) == 0, f'Unexpected bracket keys: {bracket_keys}'
print('No bracket keys: OK')

# Lookup
assert EventReg.get('navigate_to_url') is NavigateToUrlEvent
assert EventReg.get('crash') is CrashEvent
assert EventReg.get('screenshot') is ScreenshotEvent
assert EventReg.get('session_start') is SessionStartEvent
assert EventReg.get('session_end') is SessionEndEvent
print('All lookups: OK')


# ============================================================
# Verify: Pydantic functionality
# ============================================================

separator('Verify: Pydantic functionality')

nav = NavigateToUrlEvent(url='https://example.com')
print(f'model_dump:      {nav.model_dump()}')
print(f'model_dump_json: {nav.model_dump_json()}')

# model_validate from dict
nav2 = NavigateToUrlEvent.model_validate({
    'url': 'https://test.com',
    'event_id': 'custom-id',
})
assert nav2.url == 'https://test.com'
assert nav2.event_id == 'custom-id'
print(f'model_validate:  OK')

# model_copy (deep) — needed for broadcast
nav3 = nav.model_copy(deep=True)
assert nav3 is not nav
assert nav3.url == nav.url
assert nav3.event_id == nav.event_id
print(f'model_copy:      OK')

# model_validate_json
nav4 = NavigateToUrlEvent.model_validate_json(nav.model_dump_json())
assert nav4.url == nav.url
print(f'model_validate_json: OK')

# consume()
assert not nav.consumed
nav.consume()
assert nav.consumed
print(f'consume():       OK')


# ============================================================
# Verify: WAL deserialization via registry
# ============================================================

separator('Verify: WAL deserialization')

# Simulate WAL: each line is (event_type_key, json_payload)
wal_entries = [
    ('navigate_to_url', NavigateToUrlEvent(url='https://a.com').model_dump_json()),
    ('crash', CrashEvent(target_id='tab-1').model_dump_json()),
    ('screenshot', ScreenshotEvent(full_page=True).model_dump_json()),
    ('session_start', SessionStartEvent(session_id='s-001').model_dump_json()),
]

for key, json_line in wal_entries:
    cls = EventReg.get(key)
    restored = cls.model_validate_json(json_line)
    print(f'  [{key:>20}] → {type(restored).__name__}: {restored.model_dump()}')

print('WAL deserialization: OK')


# ============================================================
# Verify: Generic type info preserved
# ============================================================

separator('Verify: Generic type info')

print(f'BaseEvent orig_bases: {getattr(BaseEvent, "__orig_bases__", "N/A")}')
print(f'NavigateToUrlEvent bases: {NavigateToUrlEvent.__bases__}')
print(f'SessionStartEvent bases: {SessionStartEvent.__bases__}')
print(f'LifecycleEvent bases: {LifecycleEvent.__bases__}')


# ============================================================
# Summary
# ============================================================

separator('ALL TESTS PASSED')

print('''
Pattern: BaseEvent(BaseModel, Generic[T_Result]) + __init_subclass__
  + Clean registry — only concrete classes, no bracket keys
  + Generic[T_Result] preserved for type-safe results
  + Full Pydantic: model_dump, model_validate, model_copy, JSON
  + __abstract__ = True on intermediate bases → skipped
  + WAL deserialization via EventReg.get(key)
  - Uses conscribe internal API (_registry.add, _key_transform)

This pattern is ready for production use in the event system.
''')
