#!/usr/bin/env python3
"""Tests for the ee.deprecation module."""

import contextlib
import datetime
from typing import Any
from unittest import mock
import warnings

from absl.testing import parameterized

import unittest
from ee import apitestcase
from ee import deprecation
from ee import ee_string
from ee import image
from ee import imagecollection


_STAC_JSON = {
    'stac_version': '1.0.0',
    'type': 'Catalog',
    'id': 'GEE_catalog',
    'title': 'Google Earth Engine Catalog (Flat Version)',
    'description': 'A description.',
    'links': [
        {
            'href': 'https://example.test/catalog/catalog.json',
            'rel': 'root',
            'type': 'application/json',
        },
        {
            'href': 'https://example.test/catalog/catalog.json',
            'rel': 'self',
            'type': 'application/json',
        },
        {
            'href': 'https://example.test/date_and_learn_more.json',
            'title': 'date_and_learn_more',
            'deprecated': True,
            'gee:replacement_id': 'replacement_id',
            'gee:removal_date': '2024-07-01T00:00:00Z',
            'gee:learn_more_url': 'learn_more_url',
        },
        {
            'href': 'https://example.test/date_only.json',
            'title': 'date_only',
            'deprecated': True,
            'gee:replacement_id': 'replacement_id',
            'gee:removal_date': '2024-07-01T00:00:00Z',
        },
        {
            'href': 'https://example.test/two_digit_date.json',
            'title': 'two_digit_date',
            'deprecated': True,
            'gee:replacement_id': 'replacement_id',
            'gee:removal_date': '2024-07-01T00:00:00Z',
        },
        {
            'href': 'https://example.test/invalid_date.json',
            'title': 'invalid_date',
            'deprecated': True,
            'gee:replacement_id': 'replacement_id',
            'gee:removal_date': '20240701',
        },
        {
            'href': 'https://example.test/past_date.json',
            'title': 'past_date',
            'deprecated': True,
            'gee:replacement_id': 'replacement_id',
            'gee:removal_date': '1970-01-01T00:00:00Z',
        },
        {
            'href': 'https://example.test/learn_more_url_only.json',
            'title': 'learn_more_url_only',
            'deprecated': True,
            'gee:replacement_id': 'replacement_id',
            'gee:learn_more_url': 'learn_more_url',
        },
        {
            'href': 'https://example.test/deprecated_asset.json',
            'title': 'deprecated_asset',
            'deprecated': True,
            'gee:replacement_id': 'replacement_id',
        },
        {
            'href': 'https://example.test/non_deprecated_asset.json',
            'title': 'non_deprecated_asset',
        },
    ],
}


_EXPECTED_WARNINGS = {
    'deprecated_asset': (
        r'Attention required for deprecated_asset! You are using a deprecated'
        r' asset.\nTo make sure your code keeps working, please update it.'
    ),
    'date_and_learn_more': (
        r'Attention required for date_and_learn_more! You are using a'
        r' deprecated asset.\nTo make sure your code keeps working, please'
        r' update'
        r' it by July 1, 2024.\nThis dataset has been superseded by'
        r' replacement_id\n\nLearn more: learn_more_url'
    ),
    'date_only': (
        r'Attention required for date_only! You are using a deprecated asset.\n'
        r'To make sure your code keeps working, please update it by July 1,'
        r' 2024.'
    ),
    'two_digit_date': (
        r'Attention required for two_digit_date! You are using a deprecated'
        r' asset.\nTo make sure your code keeps working, please update it by'
        r' July 1, 2024.'
    ),
    'invalid_date': (
        r'Attention required for invalid_date! You are using a deprecated'
        r' asset.\nTo make sure your code keeps working, please update it\.'
    ),
    'past_date': (
        r'Attention required for past_date! You are using a deprecated'
        r' asset.\nTo make sure your code keeps working, please update it\.'
    ),
    'learn_more_url_only': (
        r'Attention required for learn_more_url_only! You are using a'
        r' deprecated asset.\nTo make sure your code keeps working, please'
        r' update'
        r' it.\nThis dataset has been superseded by replacement_id\n\nLearn'
        r' more: learn_more_url'
    ),
}

_MOCK_CURRENT_DATE_BEFORE_REMOVAL_DATE = datetime.datetime(
    2024, 6, 29, 12, 34, 56
)
_MOCK_CURRENT_DATE_ON_REMOVAL_DATE = datetime.datetime(2024, 7, 1, 12, 34, 56)
_MOCK_CURRENT_DATE_DAY_AFTER_REMOVAL_DATE = datetime.datetime(
    2024, 7, 2, 12, 34, 56
)

class FakeClass:

  @deprecation.WarnForDeprecatedAsset('arg1')
  def __init__(self, arg1=None, arg2=None):
    pass

  @deprecation.WarnForDeprecatedAsset('arg2')
  def some_function(self, arg1=None, arg2=None):
    pass


class MockDatetime(datetime.datetime):

  @classmethod
  def now(cls, tz=None) -> datetime.datetime:
    return _MOCK_CURRENT_DATE_BEFORE_REMOVAL_DATE


class DeprecationTest(apitestcase.ApiTestCase, parameterized.TestCase):

  def setUp(self):
    super().setUp()
    mock_datetime = mock.patch.object(
        datetime,
        'datetime',
        new=MockDatetime
    )
    self.enter_context(mock_datetime)

  @contextlib.contextmanager
  def assertDoesNotWarn(self):
    """Asserts that no warnings are thrown."""
    with warnings.catch_warnings():
      warnings.simplefilter('error')
      yield

  # Overridden from apitestcase.ApiTestCase.
  def _MockFetchDataCatalogStac(self) -> dict[str, Any]:
    return _STAC_JSON

  def test_no_warnings_thrown(self):
    with self.assertDoesNotWarn():
      FakeClass('valid-asset')

  def test_no_warnings_thrown_second_arg(self):
    with self.assertDoesNotWarn():
      FakeClass().some_function('some-value', 'valid-asset')

  @parameterized.named_parameters((k, k) for k in _EXPECTED_WARNINGS.keys())
  def test_warning_thrown_args_init(self, asset_id: str):
    with self.assertWarnsRegex(
        DeprecationWarning, _EXPECTED_WARNINGS[asset_id]
    ):
      FakeClass(asset_id, 'some-value')

  def test_warning_thrown_args_instance_method(self):
    asset = 'deprecated_asset'
    with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]):
      FakeClass().some_function('some-value', asset)

  def test_warning_thrown_kwargs_init(self):
    asset = 'deprecated_asset'
    with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]):
      FakeClass(arg1=asset)

  def test_warning_thrown_kwargs_instance_method(self):
    asset = 'deprecated_asset'
    with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]):
      FakeClass().some_function(arg2=asset)

  def test_same_warning_not_thrown(self):
    # Verifies the same warning message is not thrown twice.
    asset = 'deprecated_asset'
    with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]):
      FakeClass(arg1=asset)
    with self.assertDoesNotWarn():
      FakeClass(arg1=asset)

    # Verifies that a different warning message is thrown.
    asset = 'date_only'
    with self.assertWarnsRegex(DeprecationWarning, _EXPECTED_WARNINGS[asset]):
      FakeClass(arg1=asset)

  def test_ee_object_warning_not_thrown(self):
    with self.assertDoesNotWarn():
      FakeClass(arg1=ee_string.String('non_deprecated_asset'))
    with self.assertDoesNotWarn():
      FakeClass(
          imagecollection.ImageCollection([image.Image(0), image.Image(1)])
      )
    with self.assertDoesNotWarn():
      FakeClass(None)

  @mock.patch.object(datetime, 'datetime', autospec=True)
  def test_asset_warning_on_day_of_removal_date_shows_date(
      self, mock_datetime
  ):
    mock_datetime.now.return_value = _MOCK_CURRENT_DATE_ON_REMOVAL_DATE
    with self.assertWarnsRegex(
        DeprecationWarning, _EXPECTED_WARNINGS['date_only']
    ):
      FakeClass('date_only', 'some-value')

  @mock.patch.object(datetime, 'datetime', autospec=True)
  def test_asset_warning_on_day_after_removal_date_does_not_show_date(
      self, mock_datetime
  ):
    mock_datetime.now.return_value = _MOCK_CURRENT_DATE_DAY_AFTER_REMOVAL_DATE
    with self.assertWarnsRegex(
        DeprecationWarning,
        _EXPECTED_WARNINGS['past_date'].replace('past_date', 'date_only'),
    ):
      FakeClass('date_only', 'some-value')


if __name__ == '__main__':
  unittest.main()
