from __future__ import annotations

import dataclasses
import datetime
import json
from decimal import Decimal

import attr
import pytest
from twisted.internet import defer

from scrapy.http import Request, Response
from scrapy.utils.serialize import ScrapyJSONEncoder


class TestJsonEncoder:
    @pytest.fixture
    def encoder(self) -> ScrapyJSONEncoder:
        return ScrapyJSONEncoder(sort_keys=True)

    def test_encode_decode(self, encoder: ScrapyJSONEncoder) -> None:
        dt = datetime.datetime(2010, 1, 2, 10, 11, 12)
        dts = "2010-01-02T10:11:12"
        dt_aware = datetime.datetime(
            2010, 1, 2, 10, 11, 12, 133700, tzinfo=datetime.timezone.utc
        )
        dt_awares = "2010-01-02T10:11:12.133700+00:00"
        d = datetime.date(2010, 1, 2)
        ds = "2010-01-02"
        t = datetime.time(10, 11, 12)
        ts = "10:11:12"
        t_us = datetime.time(10, 11, 12, 133700)
        t_uss = "10:11:12.133700"
        dec = Decimal("1000.12")
        decs = "1000.12"
        s = {"foo"}
        ss = ["foo"]
        dt_set = {dt}
        dt_sets = [dts]

        for input_, output in [
            ("foo", "foo"),
            (d, ds),
            (t, ts),
            (t_us, t_uss),
            (dt, dts),
            (dt_aware, dt_awares),
            (dec, decs),
            (["foo", d], ["foo", ds]),
            (s, ss),
            (dt_set, dt_sets),
        ]:
            assert encoder.encode(input_) == json.dumps(output, sort_keys=True)

    def test_encode_deferred(self, encoder: ScrapyJSONEncoder) -> None:
        assert "Deferred" in encoder.encode(defer.Deferred())

    def test_encode_request(self, encoder: ScrapyJSONEncoder) -> None:
        r = Request("http://www.example.com/lala")
        rs = encoder.encode(r)
        assert r.method in rs
        assert r.url in rs

    def test_encode_response(self, encoder: ScrapyJSONEncoder) -> None:
        r = Response("http://www.example.com/lala")
        rs = encoder.encode(r)
        assert r.url in rs
        assert str(r.status) in rs

    def test_encode_dataclass_item(self, encoder: ScrapyJSONEncoder) -> None:
        @dataclasses.dataclass
        class TestDataClass:
            name: str
            url: str
            price: int

        item = TestDataClass(name="Product", url="http://product.org", price=1)
        encoded = encoder.encode(item)
        assert encoded == '{"name": "Product", "price": 1, "url": "http://product.org"}'

    def test_encode_attrs_item(self, encoder: ScrapyJSONEncoder) -> None:
        @attr.s
        class AttrsItem:
            name = attr.ib(type=str)
            url = attr.ib(type=str)
            price = attr.ib(type=int)

        item = AttrsItem(name="Product", url="http://product.org", price=1)
        encoded = encoder.encode(item)
        assert encoded == '{"name": "Product", "price": 1, "url": "http://product.org"}'
