"""
CDM reader
"""

import json
import requests
import re
import datetime
import functools
from xarray import open_dataset, backends
import logging
import os
from nilutility.datetime_helper import DatetimeInterval

SOURCE_NETCDF = 1
SOURCE_OPENDAP = 2
OPENDAP_BACKEND_NETCDF = 1
OPENDAP_BACKEND_PYDAP = 2

functools.lru_cache(maxsize=10)
def lrucache_get_time_dependent_metadata(self, var):
    """
    LRU chache for EbasCDMReader.get_time_dependent_metadata().
    """
    return EbasCDMReader._get_time_dependent_metadata(self, var)
    
class EbasCDMReader(object):
    """
    CDM reader class
    """

    def __init__(self, resource, opendap_backend=None):
        """
        Initialize CDM reader
        Parameters:
            resource: file path or OPeNDAP url
        """
        self.logger = logging.getLogger(__name__)
        self.resource = resource
        self.dataset = None
        if os.path.isfile(resource):
            self.logger.debug("Open local NetCDF file: %s", resource)
            self.source_type = SOURCE_NETCDF
            self.dataset = open_dataset(resource)
        elif resource.startswith('http'):
            self.source_type = SOURCE_OPENDAP
            self.backend_type = None
            self._open_opendap(resource, backend=opendap_backend)
        else:
            self.logger.error(f"Resource {resource} is not valid.")
            raise ValueError(f"Resource {resource} is not valid.")        

    def _open_opendap(self, url, backend=None):
        """
        Open the OPeNDAP url.
        
        There is a problem opening an OPeNDAP url with xarray using the
        NetCDF backend for some files/variables (many variables).

        An alternative is to use the pydap backend via xarray, but this is
        slower than the NetCDF backend.

        Caller can either choose a specific backend or use auto selection:
        In this case NetCDF is tried first, then pydap.
        The caller may use EbasCDMReader.backend_type to check which backend was
        used (e.g. for implementing some learning).

        Parameters:
            url: OPeNDAP url
            backend: backend to use (OPENDAP_BACKEND_NETCDF,
                     OPENDAP_BACKEND_PYDAP or None for auto selection)
            Returns:
                None
        """

        def _open_pydap(url):
            store = backends.PydapDataStore.open(
                url, user_charset='UTF-8')
            return open_dataset(store)

        self.logger.debug("Open OPeNDAP url: %s", url)
        if backend == OPENDAP_BACKEND_PYDAP:
            # force pydap backend
            self.dataset =_open_pydap(url)
            self.backend_type = OPENDAP_BACKEND_PYDAP
            return
        elif backend == OPENDAP_BACKEND_NETCDF:
            # force NetCDF backend
            self.dataset = open_dataset(url)
            self.backend_type = OPENDAP_BACKEND_NETCDF
            return
        # auto select backend, try NetCDF first
        try:
            self.dataset = open_dataset(url)
            self.backend_type = OPENDAP_BACKEND_NETCDF
            return
        except RuntimeError:
            # use pydap fallback
            self.dataset = _open_pydap(url)
            self.backend_type = OPENDAP_BACKEND_PYDAP
            return

    def get_time_dependent_metadata(self, var):
        """
        Get time dependent metadata
        Parameters:
            var: variable name of the base variable
        Returns:
            dict with time dependent metadata
        This uses a LRU cache, the real get is implemented in
        _get_time_dependent_metadata().
        """
        return lrucache_get_time_dependent_metadata(self, var)

    def _get_time_dependent_metadata(self, var):
        """
        
        """
        var = var + '_ebasmetadata'
        if self.source_type == SOURCE_NETCDF:
            ret = []
            for txt in self.dataset[var][:]:
                ret.aapend(json.loads(txt))
        else:  # OPeNDAP:
            # There is a problem with reading the string arrays via xarray and
            # OPeNDAP (independent of the backend used). Only the first 64
            # characters are returned. This makes the json invalid.
            # As a workaround, the metadata variables are read via an OPenDAP
            # get ascii request manually, then converted to a clean json string and
            # parsed.
            # A note on the dods request: This had anoter issue with charset
            # encoding:
            #   The test only includes on NRT dataset, so the test case can not be
            #   referenced by DOI currently, here's a long description:
            #   The test case included PS 17642 (address is "Chemin de l’Aeorologie"
            #   including \xe2\x80\x99 (unicode U+2019 RIGHT SINGLE QUOTATION MARK)
            #   (this character has no iso-1 representation).
            #   The test case also included organisation CH08L (address
            #   "Chemin de l'Aérologie" with a normal ascii single quotation mark
            #   (0x27), and additionally an é character (\xc3\xa9, unicode U+00E9)
            #   LATIN SMALL LETTER E WITH ACUTE. This character is representable in
            #   iso-1 (E9).
            # The DODS, e.g https://thredds.nilu.no/thredds/dodsC/auto-pollen_nrt/CH0001G.20240701100000.20251020235427.pollen_monitor..aerosol.68w.1h.CH08L_JUPITER_CH0001G_1_NRT.FI01L_FMI_Poleno.lev1.5.nc.dods?pollen_alnus_amean_ebasmetadata)
            # The response conent contains:
            #   for the institute b"Chemin de l\'A\xe9rologie" -- the é is iso-1 encoded
            #   for the originator b"Chemin de l?Aeorologie" -- the U+2019 is replaced by ?
            # Conclusion: the DODS request returns iso-1 encoded content, unicode
            # characters not representable in iso-1 are lost (replaced by ?).
            # --> DODS is not useable for metadata.
            #
            # Last resort: the ascii request is used, which returns utf-8 content.
            
            url = self.resource + '.ascii?' + var
            res = requests.get(url)
            if res.status_code != 200:
                self.logger.error(
                    "Failed to fetch metadata via OPeNDAP ascii request: %s",
                    url)
                raise FileNotFoundError(
                    "Failed to fetch metadata via OPeNDAP ascii request")
            txt=res.content.decode('utf8')
            txt = re.sub('^Dataset.*---------------------------------------------\n[^\n]*\n"', '[', txt, flags=re.DOTALL)
            txt = re.sub('"\n}", "{\n', '"\n},\n{\n', txt, count=0, flags=re.DOTALL+re.MULTILINE)
            txt = re.sub('"\n*$', '\n]', txt)
            ret = json.loads(txt)
        # add metadata time range to the metadata entries
        for i, times in enumerate(self.dataset['metadata_time_bnds'][:]):
            ret[i]['time_range'] = DatetimeInterval(
                times[0].values.astype('datetime64[us]').astype(
                    datetime.datetime).replace(
                        tzinfo=datetime.timezone.utc),
                times[1].values.astype('datetime64[us]').astype(
                    datetime.datetime).replace(
                        tzinfo=datetime.timezone.utc))
        return ret
