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

from collections.abc import Iterable
import contextlib
import copy
import json
import os
from typing import Any

from googleapiclient import discovery

import unittest
import ee
from ee import _cloud_api_utils
from ee import _state


# Cached algorithms list
_algorithms_cache: dict[str, Any] | None = 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: Any | None
  last_thumb_call: Any | None
  last_table_call: Any | None
  last_mapid_call: Any | None

  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-next=protected-access
    self.old_fetch_data_catalog_stac = ee.deprecation._FetchDataCatalogStac
    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
    _state.reset_state()
    # pylint: disable-next=protected-access
    ee.deprecation._FetchDataCatalogStac = self.old_fetch_data_catalog_stac

  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.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 _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: Any | None = None,
    cloud_api_resource_raw: Any | None = None,
    mock_http: Any | None = None,
) -> Iterable[Any]:  # pytype: disable=wrong-arg-types
  """Returns a context manager under which the Cloud API is enabled."""
  # pylint: disable=protected-access
  old_state = copy.copy(_state._state)
  # pylint: enable=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)
    state = _state.get_state()
    state.cloud_api_resource = cloud_api_resource
    state.cloud_api_resource_raw = cloud_api_resource_raw
    state.initialized = True
    yield
  finally:
    # pylint: disable=protected-access
    _state._state = old_state
    # pylint: enable=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'}}
            }
        ]}}
