"""Tests for DTO serialization helpers and dataclass contracts."""
from __future__ import annotations

import pytest
from datetime import datetime
from decimal import Decimal

from shared.contracts.dtos import (
    PostDTO,
    CalendarDTO,
    CalendarEntryDTO,
    TicketDTO,
    MarketPlaceDTO,
    ProductDTO,
    CartItemDTO,
    CartSummaryDTO,
    ActorProfileDTO,
    APActivityDTO,
    RemotePostDTO,
    dto_to_dict,
    dto_from_dict,
    _serialize_value,
    _unwrap_optional,
)


# ---------------------------------------------------------------------------
# _serialize_value
# ---------------------------------------------------------------------------

class TestSerializeValue:
    def test_datetime(self):
        dt = datetime(2025, 6, 15, 12, 30, 0)
        assert _serialize_value(dt) == "2025-06-15T12:30:00"

    def test_decimal(self):
        assert _serialize_value(Decimal("19.99")) == "19.99"

    def test_set_to_list(self):
        result = _serialize_value({1, 2, 3})
        assert isinstance(result, list)
        assert set(result) == {1, 2, 3}

    def test_string_passthrough(self):
        assert _serialize_value("hello") == "hello"

    def test_int_passthrough(self):
        assert _serialize_value(42) == 42

    def test_none_passthrough(self):
        assert _serialize_value(None) is None

    def test_nested_list(self):
        dt = datetime(2025, 1, 1)
        result = _serialize_value([dt, Decimal("5")])
        assert result == ["2025-01-01T00:00:00", "5"]

    def test_nested_dataclass(self):
        post = PostDTO(id=1, slug="test", title="Test", status="published", visibility="public")
        result = _serialize_value(post)
        assert isinstance(result, dict)
        assert result["slug"] == "test"


# ---------------------------------------------------------------------------
# _unwrap_optional
# ---------------------------------------------------------------------------

class TestUnwrapOptional:
    def test_optional_str(self):
        from typing import Optional
        result = _unwrap_optional(Optional[str])
        assert result is str

    def test_optional_int(self):
        from typing import Optional
        result = _unwrap_optional(Optional[int])
        assert result is int

    def test_plain_type(self):
        assert _unwrap_optional(str) is str

    def test_union_with_none(self):
        from typing import Union
        result = _unwrap_optional(Union[datetime, None])
        assert result is datetime


# ---------------------------------------------------------------------------
# dto_to_dict
# ---------------------------------------------------------------------------

class TestDtoToDict:
    def test_simple_post(self):
        post = PostDTO(id=1, slug="my-post", title="My Post", status="published", visibility="public")
        d = dto_to_dict(post)
        assert d["id"] == 1
        assert d["slug"] == "my-post"
        assert d["feature_image"] is None

    def test_with_datetime(self):
        dt = datetime(2025, 6, 15, 10, 0, 0)
        post = PostDTO(id=1, slug="s", title="T", status="published", visibility="public", published_at=dt)
        d = dto_to_dict(post)
        assert d["published_at"] == "2025-06-15T10:00:00"

    def test_with_decimal(self):
        product = ProductDTO(id=1, slug="widget", rrp=Decimal("29.99"), regular_price=Decimal("24.99"))
        d = dto_to_dict(product)
        assert d["rrp"] == "29.99"
        assert d["regular_price"] == "24.99"

    def test_cart_summary_with_items(self):
        item = CartItemDTO(id=1, product_id=10, quantity=2, unit_price=Decimal("5.00"))
        summary = CartSummaryDTO(count=1, total=Decimal("10.00"), items=[item])
        d = dto_to_dict(summary)
        assert d["count"] == 1
        assert d["total"] == "10.00"
        assert len(d["items"]) == 1
        assert d["items"][0]["product_id"] == 10

    def test_remote_post_with_nested_lists(self):
        rp = RemotePostDTO(
            id=1, remote_actor_id=5, object_id="https://example.com/1",
            content="<p>Hello</p>",
            attachments=[{"type": "Image", "url": "https://img.example.com/1.jpg"}],
            tags=[{"type": "Hashtag", "name": "#test"}],
        )
        d = dto_to_dict(rp)
        assert d["attachments"] == [{"type": "Image", "url": "https://img.example.com/1.jpg"}]


# ---------------------------------------------------------------------------
# dto_from_dict
# ---------------------------------------------------------------------------

class TestDtoFromDict:
    def test_none_data(self):
        assert dto_from_dict(PostDTO, None) is None

    def test_empty_dict(self):
        assert dto_from_dict(PostDTO, {}) is None

    def test_simple_post(self):
        d = {"id": 1, "slug": "test", "title": "Test", "status": "published", "visibility": "public"}
        post = dto_from_dict(PostDTO, d)
        assert post.slug == "test"
        assert post.feature_image is None

    def test_datetime_coercion(self):
        d = {
            "id": 1, "slug": "s", "title": "T", "status": "published",
            "visibility": "public", "published_at": "2025-06-15T10:00:00",
        }
        post = dto_from_dict(PostDTO, d)
        assert isinstance(post.published_at, datetime)
        assert post.published_at.year == 2025

    def test_decimal_coercion(self):
        d = {"id": 1, "slug": "w", "rrp": "29.99", "regular_price": 24.99}
        product = dto_from_dict(ProductDTO, d)
        assert isinstance(product.rrp, Decimal)
        assert product.rrp == Decimal("29.99")
        assert isinstance(product.regular_price, Decimal)

    def test_round_trip(self):
        dt = datetime(2025, 3, 1, 9, 30, 0)
        original = PostDTO(id=1, slug="rt", title="Round Trip", status="draft", visibility="members", published_at=dt)
        d = dto_to_dict(original)
        restored = dto_from_dict(PostDTO, d)
        assert restored.id == original.id
        assert restored.slug == original.slug
        assert restored.published_at == original.published_at

    def test_extra_keys_ignored(self):
        d = {"id": 1, "slug": "s", "title": "T", "status": "published", "visibility": "public", "extra_field": "ignored"}
        post = dto_from_dict(PostDTO, d)
        assert post.slug == "s"

    def test_calendar_entry_decimals(self):
        d = {
            "id": 1, "calendar_id": 2, "name": "Event", "start_at": "2025-07-01T14:00:00",
            "state": "confirmed", "cost": "15.00", "ticket_price": "10.50",
        }
        entry = dto_from_dict(CalendarEntryDTO, d)
        assert isinstance(entry.cost, Decimal)
        assert entry.cost == Decimal("15.00")
        assert isinstance(entry.ticket_price, Decimal)


# ---------------------------------------------------------------------------
# Frozen DTOs
# ---------------------------------------------------------------------------

class TestFrozenDTOs:
    def test_post_is_frozen(self):
        post = PostDTO(id=1, slug="s", title="T", status="published", visibility="public")
        with pytest.raises(AttributeError):
            post.title = "changed"

    def test_product_is_frozen(self):
        product = ProductDTO(id=1, slug="s")
        with pytest.raises(AttributeError):
            product.slug = "changed"

    def test_calendar_dto_defaults(self):
        cal = CalendarDTO(id=1, container_type="page", container_id=5, name="My Cal", slug="my-cal")
        assert cal.description is None

    def test_cart_summary_defaults(self):
        summary = CartSummaryDTO()
        assert summary.count == 0
        assert summary.total == Decimal("0")
        assert summary.items == []
        assert summary.ticket_count == 0
