"""Demo: Package-specific MRO scope filtering.

Tests that mro_scope=list[str] correctly controls which third-party
packages are traversed during config schema extraction.
"""
from __future__ import annotations

from typing import Any, Protocol, runtime_checkable

import httpx
from pydantic import TypeAdapter

from conscribe import build_layer_config, create_registrar, extract_config_schema


@runtime_checkable
class WorkerProtocol(Protocol):
    def run(self) -> str: ...


# ── Local parent chain (for E2E registrar test) ──

class BaseWorker:
    def __init__(self, *, max_retries: int = 3, timeout: float = 30.0, **kwargs: Any):
        self.max_retries = max_retries
        self.timeout = timeout


class MiddleWorker(BaseWorker):
    def __init__(
        self, *, batch_size: int = 10, log_level: str = "INFO", **kwargs: Any
    ):
        super().__init__(**kwargs)
        self.batch_size = batch_size
        self.log_level = log_level


# ── Scenario 1: scope=['httpx'] on httpx.Client child ──

class MyHttpClient(httpx.Client):
    def __init__(self, *, api_key: str, retry_count: int = 3, **kwargs: Any):
        super().__init__(**kwargs)
        self.api_key = api_key
        self.retry_count = retry_count


def scenario_1():
    print("=" * 60)
    print('Scenario 1: Child of httpx.Client, scope=["httpx"]')
    print("=" * 60)

    schema = extract_config_schema(MyHttpClient, mro_scope=["httpx"])
    assert schema is not None
    own = {"api_key", "retry_count"}
    httpx_fields = sorted(set(schema.model_fields.keys()) - own)

    print(f"Model:        {schema.__name__}")
    print(f"Total fields: {len(schema.model_fields)}")
    print(f"Own fields:   {sorted(own)}")
    print(f"From httpx:   {httpx_fields}")
    print(f"Extra policy: {schema.model_config.get('extra')}")
    assert len(httpx_fields) > 0, "Should collect httpx params"
    assert schema.model_config.get("extra") == "forbid", "Fully resolved → forbid"
    print("PASS\n")


# ── Scenario 2: scope=[] vs scope=['httpx'] ──

def scenario_2():
    print("=" * 60)
    print('Scenario 2: scope=[] vs scope=["httpx"]')
    print("=" * 60)

    schema_local = extract_config_schema(MyHttpClient, mro_scope=[])
    schema_httpx = extract_config_schema(MyHttpClient, mro_scope=["httpx"])
    assert schema_local is not None and schema_httpx is not None

    print(
        f'scope=[]:       {len(schema_local.model_fields):2d} fields, '
        f'extra={schema_local.model_config.get("extra")}'
    )
    print(
        f'scope=["httpx"]: {len(schema_httpx.model_fields):2d} fields, '
        f'extra={schema_httpx.model_config.get("extra")}'
    )

    gained = sorted(
        set(schema_httpx.model_fields.keys()) - set(schema_local.model_fields.keys())
    )
    print(f"Fields gained: {gained}")
    assert len(gained) > 10, "httpx.Client has many params"
    assert schema_local.model_config.get("extra") == "allow", "Truncated → allow"
    print("PASS\n")


# ── Scenario 3: scope=['nonexistent'] == scope=[] ──

def scenario_3():
    print("=" * 60)
    print('Scenario 3: scope=["nonexistent"] ≡ scope=[]')
    print("=" * 60)

    schema_empty = extract_config_schema(MyHttpClient, mro_scope=[])
    schema_none = extract_config_schema(MyHttpClient, mro_scope=["nonexistent"])
    assert schema_empty is not None and schema_none is not None

    assert sorted(schema_none.model_fields.keys()) == sorted(
        schema_empty.model_fields.keys()
    )
    print(f"Both have fields: {sorted(schema_none.model_fields.keys())}")
    print("PASS\n")


# ── Scenario 4: E2E registrar with local **kwargs chain ──

def scenario_4():
    print("=" * 60)
    print("Scenario 4: E2E registrar with local **kwargs chain")
    print("=" * 60)

    LR = create_registrar(
        "worker",
        WorkerProtocol,
        strip_suffixes=["Worker"],
        discriminator_field="type",
        mro_scope=["some_pkg"],  # list scope; all parents are local
    )

    class WorkerBase(metaclass=LR.Meta):
        __abstract__ = True

        def run(self) -> str: ...

    class FastWorker(WorkerBase, MiddleWorker):
        def __init__(self, *, speed: int = 100, **kwargs: Any):
            super().__init__(**kwargs)
            self.speed = speed

        def run(self) -> str:
            return "fast"

    class SlowWorker(WorkerBase, BaseWorker):
        def __init__(self, *, delay: float = 1.0, **kwargs: Any):
            super().__init__(**kwargs)
            self.delay = delay

        def run(self) -> str:
            return "slow"

    result = build_layer_config(LR)
    print(f"Layer: {result.layer_name}")
    print(f"Keys:  {sorted(result.per_key_models.keys())}")
    print()

    for key in sorted(result.per_key_models.keys()):
        model = result.per_key_models[key]
        print(f"  {model.__name__}:")
        for name, field in sorted(model.model_fields.items()):
            ann = (
                field.annotation.__name__
                if isinstance(field.annotation, type)
                else field.annotation
            )
            print(f"    {name}: {ann} = {field.default!r}")
        print()

    # FastWorker should collect: speed (own) + batch_size, log_level (Middle)
    #   + max_retries, timeout (Base) + type (discriminator)
    fast_model = result.per_key_models["fast"]
    assert "speed" in fast_model.model_fields, "own param"
    assert "batch_size" in fast_model.model_fields, "from MiddleWorker"
    assert "log_level" in fast_model.model_fields, "from MiddleWorker"
    assert "max_retries" in fast_model.model_fields, "from BaseWorker"
    assert "timeout" in fast_model.model_fields, "from BaseWorker"
    assert "type" in fast_model.model_fields, "discriminator"

    # SlowWorker should collect: delay (own) + max_retries, timeout (Base)
    #   + type (discriminator)
    slow_model = result.per_key_models["slow"]
    assert "delay" in slow_model.model_fields
    assert "max_retries" in slow_model.model_fields
    assert "timeout" in slow_model.model_fields

    # Validate through TypeAdapter
    adapter = TypeAdapter(result.union_type)

    fast_cfg = adapter.validate_python(
        {
            "type": "fast",
            "speed": 200,
            "batch_size": 50,
            "max_retries": 5,
            "timeout": 60.0,
            "log_level": "DEBUG",
        }
    )
    assert fast_cfg.speed == 200
    assert fast_cfg.batch_size == 50
    assert fast_cfg.max_retries == 5

    slow_cfg = adapter.validate_python(
        {"type": "slow", "delay": 2.5, "max_retries": 10}
    )
    assert slow_cfg.delay == 2.5
    assert slow_cfg.max_retries == 10
    assert slow_cfg.timeout == 30.0  # default from BaseWorker

    print("Validated FastWorker config:")
    print(f"  speed={fast_cfg.speed}, batch_size={fast_cfg.batch_size}")
    print(f"  max_retries={fast_cfg.max_retries}, log_level={fast_cfg.log_level}")
    print()
    print("Validated SlowWorker config:")
    print(f"  delay={slow_cfg.delay}, max_retries={slow_cfg.max_retries}")
    print(f"  timeout={slow_cfg.timeout}")
    print("PASS\n")


# ── Scenario 5: __config_mro_scope__ class-level list override ──

def scenario_5():
    print("=" * 60)
    print("Scenario 5: __config_mro_scope__ class-level list override")
    print("=" * 60)

    class ScopedChild(BaseWorker):
        __config_mro_scope__: Any = ["nonexistent"]

        def __init__(self, *, name: str, **kwargs: Any):
            super().__init__(**kwargs)
            self.name = name

    schema = extract_config_schema(ScopedChild, mro_scope="all")
    assert schema is not None
    print(f"Class override: __config_mro_scope__=['nonexistent']")
    print(f"Function arg:   mro_scope='all'")
    print(f"Fields:         {sorted(schema.model_fields.keys())}")

    # BaseWorker is local → included regardless of list contents
    assert "max_retries" in schema.model_fields, "local parent included"
    assert "timeout" in schema.model_fields, "local parent included"
    assert "name" in schema.model_fields, "own param"
    print("Local parent params collected despite ['nonexistent'] — correct!")
    print("PASS\n")


# ── Scenario 6: scope=['httpx'] vs scope='third_party' ──

def scenario_6():
    print("=" * 60)
    print('Scenario 6: scope=["httpx"] vs scope="third_party"')
    print("=" * 60)

    schema_list = extract_config_schema(MyHttpClient, mro_scope=["httpx"])
    schema_str = extract_config_schema(MyHttpClient, mro_scope="third_party")
    assert schema_list is not None and schema_str is not None

    list_fields = sorted(schema_list.model_fields.keys())
    str_fields = sorted(schema_str.model_fields.keys())

    print(f'scope=["httpx"]:       {len(list_fields)} fields')
    print(f'scope="third_party":   {len(str_fields)} fields')
    print(f"Same fields: {list_fields == str_fields}")

    # For this simple case (only httpx in MRO), both should be equivalent
    assert list_fields == str_fields, "same third-party package in MRO"
    print("Equivalent results — correct!")
    print("PASS\n")


if __name__ == "__main__":
    scenario_1()
    scenario_2()
    scenario_3()
    scenario_4()
    scenario_5()
    scenario_6()

    print("=" * 60)
    print("All 6 scenarios passed!")
    print("=" * 60)
