
import os
import json
from datetime import datetime
from urllib3.util.retry import Retry

import requests
from requests.adapters import HTTPAdapter
from requests_futures.sessions import FuturesSession

from usgs import USGS_API, USGSError, USGSAuthExpiredError, __version__
from usgs import payloads


TMPFILE = os.path.join(os.path.expanduser("~"), ".usgs")


def _get_api_key(api_key):

    if api_key is None and os.path.exists(TMPFILE):
        with open(TMPFILE, "r") as f:
            api_key_info = json.load(f)
        api_key = api_key_info['apiKey']

    return api_key

def _check_for_requests_error(response, message_context=""):
    try:
        response.raise_for_status()
    except requests.HTTPError as e:
        msg = f"HTTP error occurred during {message_context} request, {e}"
        raise USGSError(msg) from e

def _check_for_usgs_error(data):

    error_code = data['errorCode']
    if error_code is None:
        return

    error = data['errorMessage']

    if error_code == 'AUTH_EXPIRED':
        raise USGSAuthExpiredError("API key has expired. Try logging out and logging back in.")

    raise USGSError('%s: %s' % (error_code, error))

def _create_session(api_key):
    api_key = _get_api_key(api_key)

    headers = {
        'User-Agent': 'Python usgs v{}'.format(__version__)
    }
    if api_key:
        headers['X-Auth-Token'] = api_key

    session = requests.Session()
    session.headers.update(headers)
    retries = Retry(total=5, backoff_factor=2)
    session.mount(USGS_API, HTTPAdapter(max_retries=retries))

    return session

def dataset_filters(dataset, api_key=None):
    api_key = _get_api_key(api_key)

    with _create_session(api_key) as session:
        url = '{}/dataset-filters'.format(USGS_API)
        payload = payloads.dataset_filters(dataset)
        r = session.post(url, payload)

    _check_for_requests_error(r, "dataset filters")

    response = r.json()

    _check_for_usgs_error(response)

    return response

def download_options(dataset, entity_ids, api_key=None):
    api_key = _get_api_key(api_key)

    with _create_session(api_key) as session:
        url = '{}/download-options'.format(USGS_API)
        payload = payloads.download_options(dataset, entity_ids)
        r = session.post(url, payload)

    _check_for_requests_error(r, "download options")

    response = r.json()

    _check_for_usgs_error(response)

    return response

def dataset_download_options(dataset, api_key=None):
    """
    The dataset download options method is used to discover downloadable
    products for a specified dataset. Unlike the `download_options` method,
    this does not check product availability.

    :param str dataset: Used to identify the which dataset to return results for.
    """
    api_key = _get_api_key(api_key)

    with _create_session(api_key) as session:
        url = '{}/dataset-download-options'.format(USGS_API)
        payload = payloads.dataset_download_options(dataset)
        r = session.post(url, payload)

    _check_for_requests_error(r, "dataset download options")

    response = r.json()

    _check_for_usgs_error(response)

    return response

def download_request(dataset, entity_id, product_id, api_key=None):
    """
    This method is used to insert the requested downloads into the download queue
    and returns the available download URLs.
    """
    api_key = _get_api_key(api_key)

    with _create_session(api_key) as session:
        url = '{}/download-request'.format(USGS_API)
        payload = payloads.download_request(dataset, entity_id, product_id)
        r = session.post(url, payload)

    _check_for_requests_error(r, "download request")

    response = r.json()

    _check_for_usgs_error(response)

    return response

def dataset_search(dataset=None, catalog=None, ll=None, ur=None, start_date=None, end_date=None, api_key=None):
    api_key = _get_api_key(api_key)

    with _create_session(api_key) as session:
        url = '{}/dataset-search'.format(USGS_API)
        payload = payloads.dataset_search(
            dataset=dataset, catalog=catalog, start_date=start_date,
            end_date=end_date, ll=ll, ur=ur)
        r = session.post(url, payload)

    _check_for_requests_error(r, "dataset search")

    response = r.json()

    _check_for_usgs_error(response)

    return response

def login(username, token, save=True):
    """
    Log in, creating a temporary API key and optionally storing it for later use.

    :param str username: Username of the USGS account to log in with.
    :param str token: Application Token of the USGS account to log in with.
    :param bool save: If true, the API key will be stored in a local file (~/.usgs)
        until `api.logout` is called to remove it. The stored key will be used by
        other functions to authenticate requests whenever an API key is not explicitly
        provided.
    """
    url = '{}/login-token'.format(USGS_API)
    payload = payloads.login(username, token)

    with _create_session(api_key=None) as session:
        created = datetime.now().isoformat()
        r = session.post(url, payload)

    _check_for_requests_error(r, "login")

    response = r.json()

    _check_for_usgs_error(response)

    api_key = response["data"]

    if api_key is None:
        raise USGSError(response.get("errorMessage", "Authentication failed"))

    if save:
        with open(TMPFILE, "w") as f:
            json.dump({
                "apiKey": api_key,
                "created": created
            }, f)

    return response

def logout():
    """
    Log out by deactivating and removing the stored API key, if one exists.
    """
    if not os.path.exists(TMPFILE):
        return

    url = '{}/logout'.format(USGS_API)

    try:
        with _create_session(api_key=None) as session:
            r = session.post(url)

        _check_for_requests_error(r, "logout")

        response = r.json()
        try:
            _check_for_usgs_error(response)
        except USGSAuthExpiredError:
            pass

        return response
    finally:
        os.remove(TMPFILE)

def scene_metadata(dataset, entity_id, api_key=None):
    """
    Request metadata for a given scene in a USGS dataset.

    :param str dataset:
    :param str entity_id:
    :param str api_key:
    """
    api_key = _get_api_key(api_key)

    url = '{}/scene-metadata'.format(USGS_API)
    payload = payloads.scene_metadata(dataset, entity_id)

    with _create_session(api_key) as session:
        r = session.post(url, payload)

    _check_for_requests_error(r, "scene metadata")

    response = r.json()

    _check_for_usgs_error(response)
    return response


def scene_search(dataset,
        max_results=5000, metadata_type=None,
        start_date=None, end_date=None,
        ll=None, ur=None,
        lat=None, lng=None, distance=100,
        where=None, scene_filter=None, starting_number=1, sort_order="DESC", api_key=None):
    """
    :param str dataset:
        USGS dataset (e.g. EO1_HYP_PUB, LANDSAT_8)
    :param str lat:
        Latitude
    :param str lng:
        Longitude
    :param int distance:
        Distance in meters used to for a radial search
    :param dict ll:
        Dictionary of longitude/latitude coordinates for the lower left corner
        of a bounding box search. e.g. { "longitude": 0.0, "latitude": 0.0 }
    :param dict ur:
        Dictionary of longitude/latitude coordinates for the upper right corner
        of a bounding box search. e.g. { "longitude": 0.0, "latitude": 0.0 }
    :param str start_date:
        Start date for when a scene has been acquired
    :param str end_date:
        End date for when a scene has been acquired
    :param dict where:
        Dictionary representing key/values for finer grained conditional
        queries. Only a subset of metadata fields are supported. Available
        fields depend on the value of `dataset`, and maybe be found by
        submitting a dataset_filters query.
    :param int max_results:
        Maximum results returned by the server
    :param dict scene_filter:
        Additional filters to apply to the "sceneFilter". Can override filters set using other parameters.
        e.g. {"ingestFilter": {"start": "2010-01-01", "end": "2012-01-31"}}
    :param int starting_number:
        Starting offset for results of a query.
    :param str sort_order:
        Order in which results are sorted. Ascending or descending w.r.t the acquisition date.
    :param str api_key:
        API key for EROS. Required for searching.
    """
    api_key = _get_api_key(api_key)

    with _create_session(api_key) as session:
        url = '{}/scene-search'.format(USGS_API)
        payload = payloads.scene_search(
            dataset, max_results=max_results, metadata_type=metadata_type,
            start_date=start_date, end_date=end_date,
            ll=ll, ur=ur, lat=lat, lng=lng, distance=distance, where=where,
            scene_filter=scene_filter, starting_number=starting_number)
        r = session.post(url, payload)

    _check_for_requests_error(r, "scene search")

    response = r.json()

    _check_for_usgs_error(response)

    return response
