import pathlib
from datetime import datetime

import pandas as pd

from copernicusmarine.core_functions.deprecated_options import (
    DEPRECATED_OPTIONS,
    deprecated_python_option,
)
from copernicusmarine.core_functions.exceptions import (
    MutuallyExclusiveArguments,
)
from copernicusmarine.core_functions.models import (
    DEFAULT_COORDINATES_SELECTION_METHOD,
    DEFAULT_VERTICAL_AXIS,
    CoordinatesSelectionMethod,
    FileFormat,
    ResponseSubset,
    VerticalAxis,
)
from copernicusmarine.core_functions.request_structure import (
    create_subset_request,
)
from copernicusmarine.core_functions.subset import subset_function
from copernicusmarine.python_interface.exception_handler import (
    log_exception_and_exit,
)


@deprecated_python_option(DEPRECATED_OPTIONS)
@log_exception_and_exit
def subset(
    dataset_id: str | None = None,
    dataset_version: str | None = None,
    dataset_part: str | None = None,
    username: str | None = None,
    password: str | None = None,
    variables: list[str] | None = None,
    minimum_longitude: float | None = None,
    maximum_longitude: float | None = None,
    minimum_latitude: float | None = None,
    maximum_latitude: float | None = None,
    minimum_depth: float | None = None,
    maximum_depth: float | None = None,
    vertical_axis: VerticalAxis = DEFAULT_VERTICAL_AXIS,  # noqa
    start_datetime: datetime | pd.Timestamp | str | None = None,
    end_datetime: datetime | pd.Timestamp | str | None = None,
    minimum_x: float | None = None,
    maximum_x: float | None = None,
    minimum_y: float | None = None,
    maximum_y: float | None = None,
    coordinates_selection_method: CoordinatesSelectionMethod = (
        DEFAULT_COORDINATES_SELECTION_METHOD
    ),
    output_filename: str | None = None,
    file_format: FileFormat | None = None,
    service: str | None = None,
    request_file: pathlib.Path | str | None = None,
    output_directory: pathlib.Path | str | None = None,
    credentials_file: pathlib.Path | str | None = None,
    motu_api_request: str | None = None,
    overwrite: bool = False,
    skip_existing: bool = False,
    dry_run: bool = False,
    disable_progress_bar: bool = False,
    staging: bool = False,
    netcdf_compression_level: int = 0,
    netcdf3_compatible: bool = False,
    chunk_size_limit: int = -1,
    raise_if_updating: bool = False,
    platform_ids: list[str] | None = None,
) -> ResponseSubset:
    """
    Extract a subset of data from a specified dataset using given parameters.

    The datasetID is required and can be found via the ``describe`` command.

    Parameters
    ----------
    dataset_id : str, optional
        The datasetID, required either as an argument or in the request_file option.
    dataset_version : str, optional
        Force the selection of a specific dataset version.
    dataset_part : str, optional
        Force the selection of a specific dataset part.
    username : str, optional
        If not set, search for environment variable COPERNICUSMARINE_SERVICE_USERNAME, then search for a credentials file, else ask for user input. See also :func:`~copernicusmarine.login`
    password : str, optional
        If not set, search for environment variable COPERNICUSMARINE_SERVICE_PASSWORD, then search for a credentials file, else ask for user input. See also :func:`~copernicusmarine.login`
    variables : list[str], optional
        List of variable names to extract.
    minimum_longitude : float, optional
        Minimum longitude for the subset. The value will be transposed to the interval [-180; 360[.
    maximum_longitude : float, optional
        Maximum longitude for the subset. The value will be transposed to the interval [-180; 360[.
    minimum_latitude : float, optional
        Minimum latitude for the subset. Requires a float from -90 degrees to +90.
    maximum_latitude : float, optional
        Maximum latitude for the subset. Requires a float from -90 degrees to +90.
    minimum_x : float, optional
        Minimum x-axis value for the subset. The units are considered in length (m, 100km...).
    maximum_x : float, optional
        Maximum x-axis value for the subset. The units are considered in length (m, 100km...).
    minimum_y : float, optional
        Minimum y-axis value for the subset. The units are considered in length (m, 100km...).
    maximum_y : float, optional
        Maximum y-axis value for the subset. The units are considered in length (m, 100km...).
    minimum_depth : float, optional
        Minimum depth for the subset.
    maximum_depth : float, optional
        Maximum depth for the subset.
    vertical_axis : str, optional
        Consolidate the vertical dimension (the z-axis) as requested: depth with descending positive values, elevation with ascending positive values. Default is depth.
    start_datetime : datetime | str, optional
        The start datetime of the temporal subset. Supports common format parsed by dateutil (https://dateutil.readthedocs.io/en/stable/parser.html).
    end_datetime : datetime | str, optional
        The end datetime of the temporal subset. Supports common format parsed by dateutil (https://dateutil.readthedocs.io/en/stable/parser.html).
    coordinates_selection_method : str, optional
        If ``inside``, the selection retrieved will be inside the requested range. If ``strict-inside``, the selection retrieved will be inside the requested range, and an error will be raised if the values don't exist. If ``nearest``, the extremes closest to the requested values will be returned. If ``outside``, the extremes will be taken to contain all the requested interval. The methods ``inside``, ``nearest`` and ``outside`` will display a warning if the request is out of bounds.
    output_directory : pathlib.Path | str, optional
        The destination folder for the downloaded files. Default is the current directory.
    credentials_file : pathlib.Path | str, optional
        Path to a credentials file if not in its default directory (``$HOME/.copernicusmarine``). Accepts .copernicusmarine-credentials / .netrc or _netrc / motuclient-python.ini files.
    output_filename : str, optional
        Save the downloaded data with the given file name (under the output directory). Extension is optional and will be added if not set. Extension takes priority over the file format option if both are set.
    file_format : str, optional
        Format of the downloaded dataset. If not set or set to ``None``, defaults to NetCDF '.nc' for gridded datasets and to CSV '.csv' for sparse datasets. Output filename extension takes priority over this option if both are set.
    overwrite : bool, optional
        If specified and if the file already exists on destination, then it will be overwritten. By default, the toolbox creates a new file with a new index (eg 'filename_(1).nc').
        Mutually exclusive with ``skip_existing``.
    skip_existing : bool, optional
        If the files already exists where it would be downloaded, then the download is skipped for this file. By default, the toolbox creates a new file with a new index (eg 'filename_(1).nc').
        Mutually exclusive with ``overwrite``.
    service : str, optional
        Force download through one of the available services using the service name among ['arco-geo-series', 'arco-time-series', 'omi-arco', 'static-arco', 'arco-platform-series'] or its short name among ['geoseries', 'timeseries', 'omi-arco', 'static-arco', 'platformseries'].
    request_file : pathlib.Path | str, optional
        Option to pass a file containing the arguments. For more information please refer to the documentation or use option ``--create-template`` from the command line interface for an example template.
    motu_api_request : str, optional
        Option to pass a complete MOTU API request as a string. Caution, user has to replace double quotes " with single quotes ' in the request.
    dry_run : bool, optional
        If True, runs query without downloading data.
    netcdf_compression_level : int, optional
        Specify a compression level to apply on the NetCDF output file. A value of 0 means no compression, and 9 is the highest level of compression available.
    netcdf3_compatible : bool, optional
        Enable downloading the dataset in a netCDF3 compatible format.
    chunk_size_limit : int, default -1
        Limit the size of the chunks in the dask array. Default is set to -1 which behaves similarly to 'chunks=auto' from ``xarray``. Positive integer values and '-1' are accepted. This is an experimental feature.
    raise_if_updating : bool, default False
        If set, raises a :class:`copernicusmarine.DatasetUpdating` error if the dataset is being updated and the subset interval requested overpasses the updating start date of the dataset. Otherwise, a simple warning is displayed.
    platform_ids : list[str], optional
        List of platform IDs to extract. Only available for platform chunked datasets.

    Returns
    -------
    ResponseSubset
        A description of the downloaded data and its destination.
    """  # noqa
    if overwrite:
        if skip_existing:
            raise MutuallyExclusiveArguments("overwrite", "skip_existing")

    if variables is not None:
        _check_type(variables, list, "variables")
    if platform_ids is not None:
        _check_type(platform_ids, list, "platform_ids")

    subset_request = create_subset_request(
        dataset_id=dataset_id,
        dataset_version=dataset_version,
        dataset_part=dataset_part,
        username=username,
        password=password,
        variables=variables,
        minimum_longitude=minimum_longitude,
        maximum_longitude=maximum_longitude,
        minimum_latitude=minimum_latitude,
        maximum_latitude=maximum_latitude,
        minimum_depth=minimum_depth,
        maximum_depth=maximum_depth,
        vertical_axis=vertical_axis,
        start_datetime=start_datetime,
        end_datetime=end_datetime,
        minimum_x=minimum_x,
        maximum_x=maximum_x,
        minimum_y=minimum_y,
        maximum_y=maximum_y,
        coordinates_selection_method=coordinates_selection_method,
        output_filename=output_filename,
        file_format=file_format,
        service=service,
        request_file=(pathlib.Path(request_file) if request_file else None),
        output_directory=(
            pathlib.Path(output_directory) if output_directory else None
        ),
        credentials_file=(
            pathlib.Path(credentials_file) if credentials_file else None
        ),
        motu_api_request=motu_api_request,
        overwrite=overwrite,
        skip_existing=skip_existing,
        dry_run=dry_run,
        disable_progress_bar=disable_progress_bar,
        staging=staging,
        netcdf_compression_level=netcdf_compression_level,
        netcdf3_compatible=netcdf3_compatible,
        chunk_size_limit=chunk_size_limit,
        raise_if_updating=raise_if_updating,
        platform_ids=platform_ids,
    )

    return subset_function(
        subset_request=subset_request,
    )


def _check_type(value, expected_type: type, name: str):
    if not isinstance(value, expected_type):
        raise TypeError(f"{name} must be of type {expected_type.__name__}")
