# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pyre-strict


from __future__ import annotations

import time
import unittest
from datetime import datetime
from unittest.mock import MagicMock

from thrift.python.test.adapters.datetime import DatetimeAdapter

DatetimeAdapter_from_thrift = MagicMock(wraps=DatetimeAdapter.from_thrift)
DatetimeAdapter.from_thrift = DatetimeAdapter_from_thrift
DatetimeAdapter_to_thrift = MagicMock(wraps=DatetimeAdapter.to_thrift)
DatetimeAdapter.to_thrift = DatetimeAdapter_to_thrift
DatetimeAdapter_from_thrift_field = MagicMock(wraps=DatetimeAdapter.from_thrift_field)
DatetimeAdapter.from_thrift_field = DatetimeAdapter_from_thrift_field
DatetimeAdapter_to_thrift_field = MagicMock(wraps=DatetimeAdapter.to_thrift_field)
DatetimeAdapter.to_thrift_field = DatetimeAdapter_to_thrift_field

from python_test.adapter.thrift_types import (
    AdaptedInt,
    Bar,
    Baz,
    Datetime,
    Foo,
    NINETEEN_EIGHTY_FOUR,
)
from thrift.python.test.adapters.noop import Wrapped

# @manual=//thrift/lib/python/test:adapter_thrift-python-types
from python_test.adapter.thrift_types import (
    _fbthrift_unadapted_AsDatetime,
    _fbthrift_unadapted_Baz,
    _fbthrift_unadapted_NINETEEN_EIGHTY_FOUR,
)  # isort:skip


class AdapterTest(unittest.TestCase):
    def test_round_trip(self) -> None:
        now = datetime.fromtimestamp(int(time.time()))
        foo = Foo(created_at=now)
        self.assertIsInstance(foo.created_at, datetime)
        self.assertEqual(foo.created_at, now)

    def test_update(self) -> None:
        now = datetime.fromtimestamp(int(time.time()))
        foo = Foo(created_at=now)
        future = datetime.fromtimestamp(int(time.time()) + 100)
        foo = foo(created_at=future)
        self.assertEqual(foo.created_at, future)

    def test_union(self) -> None:
        now = datetime.fromtimestamp(int(time.time()))
        bar = Bar(ts=now)
        self.assertEqual(bar.ts, now)

    def test_struct_adapter_called_with_transitive_annotation(self) -> None:
        DatetimeAdapter_from_thrift.reset_mock()
        DatetimeAdapter_to_thrift.reset_mock()
        now_ts = int(time.time())
        now = datetime.fromtimestamp(now_ts)
        foo = Foo(updated_at=now)
        DatetimeAdapter_to_thrift.assert_called_once_with(
            now,
            transitive_annotation=_fbthrift_unadapted_AsDatetime(
                signature="DatetimeTypedef"
            ),
        )
        foo.updated_at
        DatetimeAdapter_from_thrift.assert_called_once_with(
            now_ts,
            transitive_annotation=_fbthrift_unadapted_AsDatetime(
                signature="DatetimeTypedef"
            ),
        )

    def test_field_adapter_called_with_field_id_and_trasitive_annotation(self) -> None:
        DatetimeAdapter_from_thrift_field.reset_mock()
        DatetimeAdapter_to_thrift_field.reset_mock()
        now_ts = int(time.time())
        now = datetime.fromtimestamp(now_ts)
        foo = Foo(created_at=now)
        DatetimeAdapter_to_thrift_field.assert_called_once_with(
            now,
            1,
            foo,
            transitive_annotation=_fbthrift_unadapted_AsDatetime(
                signature="DatetimeField"
            ),
        )
        foo.created_at
        DatetimeAdapter_from_thrift_field.assert_called_once_with(
            now_ts,
            1,
            foo,
            transitive_annotation=_fbthrift_unadapted_AsDatetime(
                signature="DatetimeField"
            ),
        )

        DatetimeAdapter_from_thrift_field.reset_mock()
        DatetimeAdapter_to_thrift_field.reset_mock()
        bar = Bar(ts=now)
        DatetimeAdapter_to_thrift_field.assert_called_once_with(
            now,
            2,
            bar,
            transitive_annotation=_fbthrift_unadapted_AsDatetime(
                signature="DatetimeField"
            ),
        )
        bar.ts
        DatetimeAdapter_from_thrift_field.assert_called_once_with(
            now_ts,
            2,
            bar,
            transitive_annotation=_fbthrift_unadapted_AsDatetime(
                signature="DatetimeField"
            ),
        )

    def test_typedef_field(self) -> None:
        now = datetime.fromtimestamp(int(time.time()))
        foo = Foo(updated_at=now)
        self.assertIsInstance(foo.updated_at, datetime)
        self.assertEqual(foo.updated_at, now)

    def test_double_adapters(self) -> None:
        now = datetime.fromtimestamp(int(time.time()))
        foo = Foo(another_time=now)
        self.assertIsInstance(foo.another_time, datetime)
        self.assertEqual(foo.another_time, now)

    def test_typedefs(self) -> None:
        self.assertEqual(datetime, Datetime)
        self.assertEqual(int, AdaptedInt)

    def test_adapted_type_in_containers(self) -> None:
        int_list = [1, 2, 3]
        int_list_list = [[1, 2, 3], [4, 5, 6]]
        int_set = {4, 5, 6}
        int_to_datetime_map = {42: datetime.fromtimestamp(int(time.time()))}
        foo = Foo(
            int_list=int_list,
            int_set=int_set,
            int_to_datetime_map=int_to_datetime_map,
            int_list_list=int_list_list,
        )
        self.assertEqual(foo.int_list, int_list)
        self.assertEqual(foo.int_set, int_set)
        self.assertEqual(foo.int_to_datetime_map, int_to_datetime_map)
        self.assertEqual(foo.int_list_list, int_list_list)

    def test_adapted_container_of_adapted_type(self) -> None:
        str_list = ["1", "1", "2", "3", "5", "8", "13"]
        foo = Foo(adapted_list=str_list)
        self.assertEqual(foo.adapted_list, str_list)

    def test_adapted_nested_container_of_adapted_type(self) -> None:
        str_list = [
            [{"1": "2", "3": "4"}, {"1": "5"}],
            [{"2": "6"}],
            [{"3": "5"}, {"8": "13"}],
        ]
        foo = Foo(adapted_list_nested=str_list)
        self.assertEqual(foo.adapted_list_nested, str_list)

    def test_directly_annotated(self) -> None:
        foo = Foo()
        self.assertEqual(Baz, Wrapped[_fbthrift_unadapted_Baz])
        self.assertIsInstance(foo.baz, Wrapped)
        self.assertIsInstance(foo.baz.value, _fbthrift_unadapted_Baz)

    def test_adapted_variable(self) -> None:
        self.assertEqual(NINETEEN_EIGHTY_FOUR, datetime(1984, 1, 1))
        self.assertEqual(_fbthrift_unadapted_NINETEEN_EIGHTY_FOUR, 441792000)

        DatetimeAdapter_from_thrift.assert_called_once_with(
            441792000,
            transitive_annotation=_fbthrift_unadapted_AsDatetime(
                signature="DatetimeConstant"
            ),
            constant_uri="thrift.com/python/test/NINETEEN_EIGHTY_FOUR",
        )

    def test_adapted_field_with_default_value(self) -> None:
        foo = Foo()
        self.assertIs(True, foo.wrapped_bool.value)
        self.assertIs(True, foo.double_wrapped_bool.value.value)

    def test_adapted_field_with_container_value_annotation(self) -> None:
        foo = Foo()
        self.assertIs(0, foo.abc.value)
