"""A TestCase that initializes the library with standard API methods."""

import contextlib
import json
import os
from typing import Any, Dict, Iterable, Optional

from googleapiclient import discovery

import unittest
import ee
from ee import _cloud_api_utils


# Cached algorithms list
_algorithms_cache: Optional[Dict[str, Any]] = None


def GetAlgorithms() -> Dict[str, Any]:
  """Returns a static version of the ListAlgorithms call.

  After ApiTestCase.setUp is called, ee.data.getAlgorithms() is patched to use
  this data. This function may be called explicitly if the test data is needed
  at a time before the test case has started executing.
  """
  global _algorithms_cache
  if _algorithms_cache is not None:
    return _algorithms_cache
  algorithms_path = os.path.join(
     os.path.dirname(os.path.realpath(__file__)),
     "tests/algorithms.json")
  with open(algorithms_path, encoding='utf-8') as algorithms_file:
    algorithms = json.load(algorithms_file)
    _algorithms_cache = _cloud_api_utils.convert_algorithms(algorithms)
  return _algorithms_cache


class ApiTestCase(unittest.TestCase):
  """A TestCase that initializes the library with standard API methods."""
  last_download_call: Optional[Any]
  last_thumb_call: Optional[Any]
  last_table_call: Optional[Any]
  last_mapid_call: Optional[Any]

  def setUp(self):
    super().setUp()
    # Store the state of ee.data before it is overwritten. We need to restore to
    # this state in tearDown to prevent state leakage between tests.
    self.old_get_algorithms = ee.data.getAlgorithms
    self.old_compute_value = ee.data.computeValue
    self.old_get_map_id = ee.data.getMapId
    self.old_get_download_id = ee.data.getDownloadId
    self.old_get_thumb_id = ee.data.getThumbId
    self.old_get_table_download_id = ee.data.getTableDownloadId
    # pylint: disable=protected-access
    self.old_install_cloud_api_resource = ee.data._install_cloud_api_resource
    self.old_cloud_api_resource = ee.data._cloud_api_resource
    self.old_cloud_api_resource_raw = ee.data._cloud_api_resource_raw
    self.old_initialized = ee.data._initialized
    self.old_fetch_data_catalog_stac = ee.deprecation._FetchDataCatalogStac
    # pylint: enable=protected-access
    self.InitializeApi()

  def tearDown(self):
    super().tearDown()
    ee.data.getAlgorithms = self.old_get_algorithms
    ee.data.computeValue = self.old_compute_value
    ee.data.getMapId = self.old_get_map_id
    ee.data.getDownloadId = self.old_get_download_id
    ee.data.getThumbId = self.old_get_thumb_id
    ee.data.getTableDownloadId = self.old_get_table_download_id
    # pylint: disable=protected-access
    ee.data._install_cloud_api_resource = self.old_install_cloud_api_resource
    ee.data._cloud_api_resource = self.old_cloud_api_resource
    ee.data._cloud_api_resource_raw = self.old_cloud_api_resource_raw
    ee.data._initialized = self.old_initialized
    ee.deprecation._FetchDataCatalogStac = self.old_fetch_data_catalog_stac
    # pylint: enable=protected-access

  def InitializeApi(self):
    """Initializes the library with standard API methods.

    This is normally invoked during setUp(), but subclasses may invoke
    it manually instead if they prefer.
    """
    self.last_download_call = None
    self.last_thumb_call = None
    self.last_table_call = None
    self.last_mapid_call = None

    ee.Reset()

    ee.data._install_cloud_api_resource = lambda: None  # pylint: disable=protected-access
    ee.data.getAlgorithms = GetAlgorithms
    ee.data.computeValue = lambda x: {'value': 'fakeValue'}
    ee.data.getMapId = self._MockMapId
    ee.data.getDownloadId = self._MockDownloadUrl
    ee.data.getThumbId = self._MockThumbUrl
    ee.data.getTableDownloadId = self._MockTableDownload
    # pylint: disable-next=protected-access
    ee.deprecation._FetchDataCatalogStac = self._MockFetchDataCatalogStac
    ee.Initialize(None, '', project='my-project')

  # We are mocking the url here so the unit tests are happy.
  def _MockMapId(self, params: Dict[str, Any]) -> Dict[str, str]:
    self.last_mapid_call = {'url': '/mapid', 'data': params}
    return {'mapid': 'fakeMapId', 'token': 'fakeToken'}

  def _MockDownloadUrl(self, params: Dict[str, Any]) -> Dict[str, str]:
    self.last_download_call = {'url': '/download', 'data': params}
    return {'docid': '1', 'token': '2'}

  def _MockThumbUrl(
      self,
      params: Dict[str, Any],
      # pylint: disable-next=invalid-name
      thumbType: Optional[str] = None,
  ) -> Dict[str, str]:
    del thumbType  # Unused.
    # Hang on to the call arguments.
    self.last_thumb_call = {'url': '/thumb', 'data': params}
    return {'thumbid': '3', 'token': '4'}

  def _MockTableDownload(self, params: Dict[str, Any]) -> Dict[str, str]:
    self.last_table_call = {'url': '/table', 'data': params}
    return {'docid': '5', 'token': '6'}

  def _MockFetchDataCatalogStac(self) -> Dict[str, Any]:
    return {}


def _GenerateCloudApiResource(mock_http: Any, raw: Any) -> discovery.Resource:
  """Returns a Cloud API resource for testing."""
  discovery_doc_path = os.path.join(
     os.path.dirname(os.path.realpath(__file__)),
     "tests/cloud_api_discovery_document.json")
  with open(discovery_doc_path) as discovery_doc_file:
    discovery_doc_str = discovery_doc_file.read()
  return _cloud_api_utils.build_cloud_resource_from_document(
      json.loads(discovery_doc_str),
      http_transport=mock_http,
      headers_supplier=ee.data._make_request_headers,  # pylint: disable=protected-access
      response_inspector=ee.data._handle_profiling_response,  # pylint: disable=protected-access
      raw=raw,
  )


@contextlib.contextmanager  # pytype: disable=wrong-arg-types
def UsingCloudApi(
    cloud_api_resource: Optional[Any] = None,
    cloud_api_resource_raw: Optional[Any] = None,
    mock_http: Optional[Any] = None,
) -> Iterable[Any]:  # pytype: disable=wrong-arg-types
  """Returns a context manager under which the Cloud API is enabled."""
  old_cloud_api_resource = ee.data._cloud_api_resource  # pylint: disable=protected-access
  old_cloud_api_resource_raw = ee.data._cloud_api_resource_raw  # pylint: disable=protected-access
  old_initialized = ee.data._initialized  # pylint: disable=protected-access
  try:
    if cloud_api_resource is None:
      cloud_api_resource = _GenerateCloudApiResource(mock_http, False)
    if cloud_api_resource_raw is None:
      cloud_api_resource_raw = _GenerateCloudApiResource(mock_http, True)
    ee.data._cloud_api_resource = cloud_api_resource  # pylint: disable=protected-access
    ee.data._cloud_api_resource_raw = cloud_api_resource_raw  # pylint: disable=protected-access
    ee.data._initialized = True  # pylint: disable=protected-access
    yield
  finally:
    ee.data._cloud_api_resource = old_cloud_api_resource  # pylint: disable=protected-access
    ee.data._cloud_api_resource_raw = old_cloud_api_resource_raw  # pylint: disable=protected-access
    ee.data._initialized = old_initialized  # pylint: disable=protected-access


# A sample of encoded EE API JSON, used by SerializerTest and DeserializerTest.
ENCODED_JSON_SAMPLE = {
    'type': 'CompoundValue',
    'scope': [
        ['0', {
            'type': 'Invocation',
            'functionName': 'Date',
            'arguments': {
                'value': 1234567890000
            }
        }],
        ['1', {
            'type': 'LineString',
            'coordinates': [[1, 2], [3, 4]],
            'crs': {
                'type': 'name',
                'properties': {
                    'name': 'SR-ORG:6974'
                }
            }
        }],
        ['2', {
            'evenOdd': True,
            'type': 'Polygon',
            'coordinates': [
                [[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]],
                [[5, 6], [7, 6], [7, 8], [5, 8]],
                [[1, 1], [2, 1], [2, 2], [1, 2]]
            ]
        }],
        ['3', {
            'type': 'Bytes',
            'value': 'aGVsbG8='
        }],
        ['4', {
            'type': 'Invocation',
            'functionName': 'String.cat',
            'arguments': {
                'string1': 'x',
                'string2': 'y'
            }
        }],
        ['5', {
            'type': 'Dictionary',
            'value': {
                'foo': 'bar',
                'baz': {'type': 'ValueRef', 'value': '4'}
            }
        }],
        ['6', {
            'type': 'Function',
            'argumentNames': ['x', 'y'],
            'body': {'type': 'ArgumentRef', 'value': 'y'}
        }],
        ['7', [
            None,
            True,
            5,
            7,
            3.4,
            112233445566778899,
            'hello',
            {'type': 'ValueRef', 'value': '0'},
            {'type': 'ValueRef', 'value': '1'},
            {'type': 'ValueRef', 'value': '2'},
            {'type': 'ValueRef', 'value': '3'},
            {'type': 'ValueRef', 'value': '5'},
            {'type': 'ValueRef', 'value': '4'},
            {'type': 'ValueRef', 'value': '6'}
        ]]
    ],
    'value': {'type': 'ValueRef', 'value': '7'}
}

# A sample of encoded EE API JSON for the Cloud API, used by SerializerTest.
ENCODED_CLOUD_API_JSON_SAMPLE = {
    'values': {
        '0': {
            'arrayValue': {
                'values': [
                    {'constantValue': None},
                    {'constantValue': True},
                    {'constantValue': 5},
                    {'constantValue': 7},
                    {'constantValue': 3.4},
                    {'integerValue': '112233445566778899'},
                    {'constantValue': 'hello'},
                    {'functionInvocationValue': {
                        'functionName': 'Date',
                        'arguments': {'value': {'constantValue': 1234567890000}}
                    }},
                    {'functionInvocationValue': {
                        'functionName': 'GeometryConstructors.LineString',
                        'arguments': {
                            'crs': {'functionInvocationValue': {
                                'functionName': 'Projection',
                                'arguments': {
                                    'crs': {'constantValue': 'SR-ORG:6974'}}
                            }},
                            'coordinates': {'arrayValue': {'values': [
                                {'valueReference': '1'},
                                {'constantValue': [3, 4]}
                            ]}}
                        }}},
                    {'functionInvocationValue': {
                        'functionName': 'GeometryConstructors.Polygon',
                        'arguments': {
                            'coordinates': {'arrayValue': {'values': [
                                {'arrayValue': {'values': [
                                    {'valueReference': '2'},
                                    {'constantValue': [10, 0]},
                                    {'constantValue': [10, 10]},
                                    {'constantValue': [0, 10]},
                                    {'valueReference': '2'}]}},
                                {'constantValue':
                                 [[5, 6], [7, 6], [7, 8], [5, 8]]},
                                {'arrayValue': {'values': [
                                    {'constantValue': [1, 1]},
                                    {'constantValue': [2, 1]},
                                    {'constantValue': [2, 2]},
                                    {'valueReference': '1'}]}}
                            ]}},
                            'evenOdd': {'constantValue': True}}}},
                    {'bytesValue': 'aGVsbG8='},
                    {'dictionaryValue': {
                        'values': {
                            'baz': {'valueReference': '3'},
                            'foo': {'constantValue': 'bar'},
                        }
                    }},
                    {'valueReference': '3'},
                    {'functionDefinitionValue': {
                        'argumentNames': ['x', 'y'],
                        'body': '4'}
                    }
                ]}},
        '1': {'constantValue': [1, 2]},
        '2': {'constantValue': [0, 0]},
        '3': {'functionInvocationValue': {
            'functionName': 'String.cat',
            'arguments': {
                'string1': {'constantValue': 'x'},
                'string2': {'constantValue': 'y'}
            }}},
        '4': {'argumentReference': 'y'},
    },
    'result': '0'
}
ENCODED_CLOUD_API_JSON_SAMPLE_PRETTY = {
    'arrayValue': {
        'values': [
            {'constantValue': None},
            {'constantValue': True},
            {'constantValue': 5},
            {'constantValue': 7},
            {'constantValue': 3.4},
            {'integerValue': '112233445566778899'},
            {'constantValue': 'hello'},
            {'functionInvocationValue': {
                'functionName': 'Date',
                'arguments': {'value': {'constantValue': 1234567890000}}
            }},
            {'functionInvocationValue': {
                'functionName': 'GeometryConstructors.LineString',
                'arguments': {
                    'crs': {'functionInvocationValue': {
                        'functionName': 'Projection',
                        'arguments': {
                            'crs': {'constantValue': 'SR-ORG:6974'}}
                    }},
                    'coordinates': {'constantValue': [[1, 2], [3, 4]]}
                }}},
            {'functionInvocationValue': {
                'functionName': 'GeometryConstructors.Polygon',
                'arguments': {
                    'coordinates': {
                        'constantValue':
                        [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]],
                         [[5, 6], [7, 6], [7, 8], [5, 8]],
                         [[1, 1], [2, 1], [2, 2], [1, 2]]]},
                    'evenOdd': {'constantValue': True}}}},
            {'bytesValue': 'aGVsbG8='},
            {'dictionaryValue': {
                'values': {
                    'baz': {'functionInvocationValue': {
                        'functionName': 'String.cat',
                        'arguments': {
                            'string1': {'constantValue': 'x'},
                            'string2': {'constantValue': 'y'}
                        }}},
                    'foo': {'constantValue': 'bar'},
                }}},
            {'functionInvocationValue': {
                'functionName': 'String.cat',
                'arguments': {
                    'string1': {'constantValue': 'x'},
                    'string2': {'constantValue': 'y'}
                }}},
            {'functionDefinitionValue': {
                'argumentNames': ['x', 'y'],
                'body': {'argumentReference': 'y'}}
            }
        ]}}
