import httpx
import pytest
from pydantic import BaseModel

from aiyou_client.common import ResultResponse, patch_response_class
from aiyou_client.dispatcher import AsyncDispatcherClient
from aiyou_client.exception import RCError
from aiyou_client.logger import logger


class _ItemModel(BaseModel):
  value: int


@pytest.mark.anyio
async def test_client_uses_explicit_base_url_and_headers():
  requests: list[httpx.Request] = []

  async def handler(request: httpx.Request) -> httpx.Response:
    requests.append(request)
    return httpx.Response(200, json={'ok': True})

  client = AsyncDispatcherClient(
    base_url='https://gateway.example.com',
    headers={'X-Service': 'dispatcher-service'},
    transport=httpx.MockTransport(handler),
  )

  try:
    response = await client.get('/health')
  finally:
    await client.aclose()

  assert isinstance(response, ResultResponse)
  assert response.status_code == 200
  assert response.json() == {'ok': True}
  assert requests[0].url == httpx.URL('https://gateway.example.com/health')
  assert requests[0].headers['x-service'] == 'dispatcher-service'


@pytest.mark.anyio
async def test_request_collector_token_overrides_authorization():
  auth_headers: list[str | None] = []

  async def handler(request: httpx.Request) -> httpx.Response:
    auth_headers.append(request.headers.get('authorization'))
    return httpx.Response(200, json={'ok': True})

  transport = httpx.MockTransport(handler)
  client = AsyncDispatcherClient(
    token='base-token',
    base_url='https://dispatcher.example.com',
    transport=transport,
  )
  try:
    await client.get('/health')
    await client.get('/health', collector_token='child-token')
  finally:
    await client.aclose()

  assert auth_headers == ['base-token', 'child-token']


@pytest.mark.anyio
async def test_init_collector_token_sets_default_authorization():
  auth_headers: list[str | None] = []

  async def handler(request: httpx.Request) -> httpx.Response:
    auth_headers.append(request.headers.get('authorization'))
    return httpx.Response(200, json={'ok': True})

  client = AsyncDispatcherClient(
    collector_token='base-token',
    base_url='https://dispatcher.example.com',
    transport=httpx.MockTransport(handler),
  )
  try:
    await client.get('/health')
    await client.get('/health', collector_token='child-token')
  finally:
    await client.aclose()

  assert auth_headers == ['base-token', 'child-token']


@pytest.mark.anyio
async def test_anything_retrieve_marks_rc_response():
  requests: list[httpx.Request] = []

  async def handler(request: httpx.Request) -> httpx.Response:
    requests.append(request)
    return httpx.Response(
      200,
      json={
        'rc': 0,
        'result': {'reciprocal_rank_fusion': [{'content': '西湖适合慢游。'}]},
      },
    )

  client = AsyncDispatcherClient(
    base_url='https://dispatcher.example.com',
    transport=httpx.MockTransport(handler),
  )

  try:
    response = await client.chatmdt.anything.retrieve(
      'anything-uuid',
      {'retrievers': {'reciprocal_rank_fusion': {'content': '西湖'}}},
    )
  finally:
    await client.aclose()

  assert response.rc_response is True
  assert response.rc_result_or_none() == {
    'reciprocal_rank_fusion': [{'content': '西湖适合慢游。'}]
  }
  assert requests[0].url == httpx.URL(
    'https://dispatcher.example.com/chatmdt/anything/anything-uuid/retrieve'
  )


@pytest.mark.anyio
async def test_wrapped_response_with_config_supports_pydantic_parsing():
  async def handler(_: httpx.Request) -> httpx.Response:
    return httpx.Response(200, json={'value': 7})

  client = AsyncDispatcherClient(
    base_url='https://dispatcher.example.com',
    transport=httpx.MockTransport(handler),
  )

  try:
    response = await client.get('/value')
  finally:
    await client.aclose()

  parsed = response.with_config(model=_ItemModel).pydantic_or_raise()

  assert parsed.value == 7


@pytest.mark.anyio
async def test_wrapped_response_with_config_supports_rc_unwrapping():
  async def handler(_: httpx.Request) -> httpx.Response:
    return httpx.Response(200, json={'rc': 0, 'result': {'value': 8}})

  client = AsyncDispatcherClient(
    base_url='https://dispatcher.example.com',
    transport=httpx.MockTransport(handler),
  )

  try:
    response = await client.get('/wrapped')
  finally:
    await client.aclose()

  assert response.with_config(rc_response=True).json_or_raise() == {'value': 8}


@pytest.mark.anyio
async def test_collector_permission_read_uses_dispatcher_route():
  requests: list[httpx.Request] = []

  async def handler(request: httpx.Request) -> httpx.Response:
    requests.append(request)
    return httpx.Response(200, json={'rc': 0, 'result': []})

  client = AsyncDispatcherClient(
    base_url='https://dispatcher.example.com',
    transport=httpx.MockTransport(handler),
  )

  try:
    response = await client.collector.permission.read(
      patterns=['dispatch/service:openai']
    )
  finally:
    await client.aclose()

  assert response.rc_response is True
  assert requests[0].url == httpx.URL(
    'https://dispatcher.example.com/permission/read'
  )
  assert requests[0].read() == (
    b'{"with_extra":false,"page_size":100,"page_num":0,'
    b'"patterns":["dispatch/service:openai"]}'
  )


@pytest.mark.anyio
async def test_collector_role_relation_uses_dispatcher_route():
  requests: list[httpx.Request] = []

  async def handler(request: httpx.Request) -> httpx.Response:
    requests.append(request)
    return httpx.Response(200, json={'rc': 0, 'result': {'count': 1}})

  client = AsyncDispatcherClient(
    base_url='https://dispatcher.example.com',
    transport=httpx.MockTransport(handler),
  )

  try:
    response = await client.collector.role.relation(
      [{'target_uuid': 'role', 'source_uuid': 'user', 'relation': 'inherit'}]
    )
  finally:
    await client.aclose()

  assert response.rc_response is True
  assert requests[0].url == httpx.URL(
    'https://dispatcher.example.com/role/relation'
  )


@pytest.mark.anyio
async def test_patch_response_class_reconfigures_wrapped_response():
  @patch_response_class(model=_ItemModel)
  async def _wrapped():
    return ResultResponse.from_dict({'value': 9})

  response = await _wrapped()

  assert response.pydantic_or_raise().value == 9


@pytest.mark.anyio
async def test_transport_errors_still_return_wrapped_response():
  client = AsyncDispatcherClient(
    base_url='https://dispatcher.example.com',
    transport=httpx.MockTransport(
      lambda request: (_ for _ in ()).throw(httpx.ReadTimeout('timeout'))
    ),
  )

  try:
    response = await client.get('/timeout')
  finally:
    await client.aclose()

  assert isinstance(response, ResultResponse)
  assert response.status_code == 599
  assert response.json() == {'detail': 'upstream read timeout'}
  with pytest.raises(httpx.HTTPStatusError):
    response.raise_for_status()


def test_result_response_handles_rc_payloads():
  result = ResultResponse.from_dict(
    {'rc': 0, 'result': {'value': 3}},
    model=_ItemModel,
  )
  result.rc_response = True

  parsed = result.pydantic_or_raise()

  assert parsed.value == 3


def test_json_or_raise_returns_payload():
  result = ResultResponse.from_dict({'value': 11})

  assert result.json_or_raise() == {'value': 11}


def test_text_and_content_or_raise_delegate_to_httpx_response():
  response = httpx.Response(
    200,
    text='ok',
    request=httpx.Request('GET', 'http://example.com'),
  )
  result = ResultResponse(response)

  assert result.text_or_raise() == 'ok'
  assert result.content_or_raise() == b'ok'


def test_raise_for_status_delegates_to_httpx_response():
  result = ResultResponse.from_dict(
    {'detail': 'not found'},
    status_code=404,
  )

  with pytest.raises(httpx.HTTPStatusError):
    result.raise_for_status()


def test_rc_result_or_none_unwraps_result_payload():
  result = ResultResponse.from_dict({'rc': 0, 'result': {'value': 3}})

  assert result.rc_result_or_none() == {'value': 3}


def test_rc_result_or_none_returns_none_when_rc_is_not_zero():
  result = ResultResponse.from_dict(
    {'rc': 1001, 'message': 'bad request', 'result': {'value': 3}}
  )

  assert result.rc_result_or_none() is None


def test_rc_result_or_raise_raises_for_non_zero_rc():
  result = ResultResponse.from_dict(
    {'rc': 1001, 'message': 'bad request', 'result': {'value': 3}}
  )

  with pytest.raises(RCError):
    result.rc_result_or_raise()


def test_result_response_returns_none_for_invalid_payload():
  result = ResultResponse.from_dict({'value': 'bad'}, model=_ItemModel)

  assert result.pydantic_or_none() is None


def test_pydantic_list_or_raise_parses_all_items():
  result = ResultResponse.from_dict(
    [{'value': 1}, {'value': 2}],
    model=_ItemModel,
  )

  parsed = result.pydantic_list_or_raise()

  assert [item.value for item in parsed] == [1, 2]


def test_removed_constructor_flags_raise_type_error():
  with pytest.raises(TypeError):
    AsyncDispatcherClient(skip_status_handle=True)


def test_package_logger_uses_aiyou_client_namespace():
  assert logger.name == 'aiyou_client'
