"""This module generates UMM-Var records for a specified collection, and
optionally will publish those records to CMR.
`generation_collection_umm_var` will:

* Performing a CMR granule search with collection-based query parameters.
* Downloading the granule locally.
* Parse the variable metadata from the granule using VarInfoFromNetCDF4.
* Generate UMM-Var JSON as a dictionary for each identified variable.
* (Optional) Publish each UMM-Var entry to the selected CMR environment.

"""

from __future__ import annotations

from tempfile import TemporaryDirectory
from typing import Union
import re

from cmr import CMR_UAT

from varinfo import VarInfoFromNetCDF4, VarInfoFromDmr
from varinfo.cmr_search import (
    CmrEnvType,
    download_granule,
    get_granule_link,
    get_granules,
    get_edl_token_header,
    get_dmr_xml_url,
)
from varinfo.umm_var import get_all_umm_var, publish_all_umm_var


# Custom return type: either a list of UMM-Var JSON (a list of dictionaries),
# or a list of strings (either concept IDs or error strings).
UmmVarReturnType = list[Union[dict, str]]


def generate_collection_umm_var(
    collection_concept_id: str,
    auth_header: str,
    cmr_env: CmrEnvType = CMR_UAT,
    publish: bool = False,
    config_file: str | None = None,
    use_dmr: bool = False,
) -> UmmVarReturnType:
    """Run all the of the functions for downloading and publishing
    a UMM-Var entry to CMR given:

    * collection_concept_id: Concept ID for collection that variables have
      been generated for.
    * cmr_env: URLs for CMR environments (OPS, UAT, and SIT)
    * auth_header: Authorization HTTP header, containing a LaunchPad
      token: 'Authorization: <token>'
    * publish: Optional argument determining whether to publish the
      generated UMM-Var records to the indicated CMR instance. Defaults to
      False.
    * config_file: Optional argument to provide a configuration file that
      could be used to override any known errors in a collection. Defaults to None
    Note - if attempting to publish to CMR, a LaunchPad token must be used.

    """
    # Get an EDL token and its header given a LaunchPad token
    auth_header_edl_token = get_edl_token_header(auth_header, cmr_env)

    granule_response = get_granules(
        collection_concept_id, cmr_env=cmr_env, auth_header=auth_header_edl_token
    )

    if use_dmr:
        # Get OPeNDAP data URL with `.dml.xml` appended
        granule_link = get_dmr_xml_url(granule_response)

    else:
        # Get the data download URL for the most recent granule (NetCDF-4 file)
        granule_link = get_granule_link(granule_response)

    with TemporaryDirectory() as temp_dir:
        # Download file to runtime environment
        local_granule = download_granule(
            granule_link, auth_header_edl_token, out_directory=temp_dir
        )

        if use_dmr:
            # Parse the granule with VarInfoFromDmr to map all variables and relations:
            var_info = VarInfoFromDmr(local_granule, config_file=config_file)
        else:
            # Parse the granule with VarInfoFromNetCDF4 to map all variables and relations:
            var_info = VarInfoFromNetCDF4(local_granule, config_file=config_file)

    # Generate all the UMM-Var records:
    all_umm_var_records = get_all_umm_var(var_info)

    if publish:
        # Publish to CMR and construct an output object that is a list of
        # strings. These strings will be either variable concept IDs or
        # error messages returned from CMR.
        publication_response = publish_all_umm_var(
            collection_concept_id, all_umm_var_records, auth_header, cmr_env
        )

        # Produce a list indicating publication information for all variables.
        # Variables that were successfully published will have a list element
        # providing their variable concept ID. Any variables that were
        # not successfully published will instead have an element containing
        # the variable name and the CMR error (e.g., 'variable: CMR error...').
        return_value = [
            (
                variable_response
                if is_variable_concept_id(variable_response)
                else ': '.join([variable_name, variable_response])
            )
            for variable_name, variable_response in publication_response.items()
        ]
    else:
        # If not publishing, return the full UMM-Var JSON records
        return_value = list(all_umm_var_records.values())

    return return_value


def is_variable_concept_id(possible_concept_id: str) -> bool:
    """A helper function to identify if a given string conforms to the
    expected structure of a variable concept ID, e.g., 'V1234567890-PROV'.

    """
    return bool(re.match(r'^V\d{10}-\w+$', possible_concept_id))
