"""
This module is designed to make STCAL tweakreg and resample functions accessible without
installing the original package due to their complex dependencies. As such, it is only subtly modified from
the original to accommodate the less stringent install requirements.


STCAL copyright notice:

Copyright (C) 2020 Association of Universities for Research in Astronomy (AURA)

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.

    3. The name of AURA and its representatives may not be used to
      endorse or promote products derived from this software without
      specific prior written permission.

THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.


Original: https://github.com/spacetelescope/stcal/blob/main/src/stcal/tweakreg/astrometric_utils.py
"""

import os

import requests
from astropy import table
from astropy import units as u
from astropy.coordinates import SkyCoord
from astropy.table import Table

from spike.stcal.alignment_util import compute_fiducial

ASTROMETRIC_CAT_ENVVAR = "ASTROMETRIC_CATALOG_URL"
DEF_CAT_URL = "http://gsss.stsci.edu/webservices"

SERVICELOCATION = os.environ.get(ASTROMETRIC_CAT_ENVVAR, DEF_CAT_URL)

TIMEOUT = 30.0  # in seconds

"""

Primary function for creating an astrometric reference catalog.

"""


__all__ = [
    "TIMEOUT",
    "compute_radius",
    "create_astrometric_catalog",
    "get_catalog"]


def create_astrometric_catalog(
        wcs,
        epoch,
        catalog="GAIADR3",
        output="ref_cat.ecsv",
        gaia_only=False,
        table_format="ascii.ecsv",
        num_sources=None):
    """Create an astrometric catalog that covers the inputs' field-of-view.

    Parameters
    ----------
    wcs : `~astropy.wcs.WCS`
        WCS object specified by the user as generated by
        `resample.resample_utils.make_output_wcs`. This will typically
        have the same plate-scale and orientation as the first member in the
        list of input images to make_output_wcs. Fortunately, for alignment,
        this doesn't matter since no resampling of data will be performed.

    epoch : float
        Reference epoch used to update the coordinates for proper motion
        (in decimal year).

    catalog : str, optional
        Name of catalog to extract astrometric positions for sources in the
        input images' field-of-view. Default: GAIADR3. Options available are
        documented on the catalog web page.

    output : str, optional
        Filename to give to the astrometric catalog read in from the master
        catalog web service.  If None, no file will be written out.

    gaia_only : bool, optional
        Specify whether or not to only use sources from GAIA in output catalog

    num_sources : int
        Maximum number of brightest/faintest sources to return in catalog.
        If `num_sources` is negative, return that number of the faintest
        sources.  By default, all sources are returned.



    Notes
    -----
    This function will point to astrometric catalog web service defined
    through the use of the ASTROMETRIC_CATALOG_URL environment variable.

    Returns
    -------
    ref_table : `~astropy.table.Table`
        Astropy Table object of the catalog
    """
    # start by creating a composite field-of-view for all inputs
    radius, fiducial = compute_radius(wcs)

    # perform query for this field-of-view
    ref_dict = get_catalog(
        fiducial[0],
        fiducial[1],
        epoch=epoch,
        search_radius=radius,
        catalog=catalog)
    if len(ref_dict) == 0:
        return ref_dict

    colnames = ("ra", "dec", "mag", "objID", "epoch")
    ref_table = ref_dict[colnames]

    # Add catalog name as meta data
    ref_table.meta["catalog"] = catalog
    ref_table.meta["gaia_only"] = gaia_only

    # rename coordinate columns to be consistent with tweakwcs
    ref_table.rename_column("ra", "RA")
    ref_table.rename_column("dec", "DEC")

    # Append GAIA ID as a new column to the table...
    gaia_sources = []
    for source in ref_dict:
        if "GAIAsourceID" in source:
            g = source["GAIAsourceID"]
            if gaia_only and g.strip() == "":
                continue
        else:
            g = "-1"  # indicator for no source ID extracted
        gaia_sources.append(g)

    gaia_col = table.Column(data=gaia_sources, name="GaiaID", dtype="U25")
    ref_table.add_column(gaia_col)

    # sort table by magnitude, fainter to brightest
    ref_table.sort("mag", reverse=True)

    # If specified by the use through the 'num_sources' parameter,
    # trim the returned catalog down to the brightest 'num_sources' sources
    # Should 'num_sources' be a negative value, it will return the faintest
    # 'num_sources' sources.
    if num_sources is not None:
        indx = -1 * num_sources
        ref_table = ref_table[:indx] if num_sources < 0 else ref_table[indx:]

    # Write out table to a file, if specified
    if output is not None:
        ref_table.write(output, format=table_format, overwrite=True)

    return ref_table


def compute_radius(wcs):
    """Compute the radius from the center to the furthest edge of the WCS."""
    fiducial = compute_fiducial([wcs,])
    img_center = SkyCoord(
        ra=fiducial[0] * u.degree,
        dec=fiducial[1] * u.degree)
    wcs_foot = wcs.footprint()
    img_corners = SkyCoord(ra=wcs_foot[:, 0] * u.degree,
                           dec=wcs_foot[:, 1] * u.degree)
    radius = img_center.separation(img_corners).max().value

    return radius, fiducial


def get_catalog(
    right_ascension,
    declination,
    epoch=2016.0,
    search_radius=0.1,
    catalog="GAIADR3",
    timeout=TIMEOUT,
):
    """Extract catalog from VO web service.

    Parameters
    ----------
    right_ascension : float
        Right Ascension (RA) of center of field-of-view (in decimal degrees)

    declination : float
        Declination (Dec) of center of field-of-view (in decimal degrees)

    epoch : float, optional
        Reference epoch used to update the coordinates for proper motion
        (in decimal year). Default: 2016.0

    search_radius : float, optional
        Search radius (in decimal degrees) from field-of-view center to use
        for sources from catalog.  Default: 0.1 degrees

    catalog : str, optional
        Name of catalog to query, as defined by web-service. Default: 'GAIADR3'

    timeout : float, optional
        Timeout in seconds to wait for the catalog web service to respond. Default: 30.0 s

    Returns
    -------
    csv : `~astropy.table.Table`
        CSV object of returned sources with all columns as provided by catalog

    """
    service_type = "vo/CatalogSearch.aspx"
    spec_str = "RA={}&DEC={}&EPOCH={}&SR={}&FORMAT={}&CAT={}&MINDET=5"
    headers = {"Content-Type": "text/csv"}
    fmt = "CSV"

    spec = spec_str.format(
        right_ascension,
        declination,
        epoch,
        search_radius,
        fmt,
        catalog
    )
    service_url = f"{SERVICELOCATION}/{service_type}?{spec}"
    try:
        rawcat = requests.get(service_url, headers=headers, timeout=timeout)
    except requests.exceptions.ConnectionError:
        raise requests.exceptions.ConnectionError(
            "Could not connect to the VO API server. Try again later."
        )
    except requests.exceptions.Timeout:
        raise requests.exceptions.Timeout("The request to the VO API server timed out.")
    except requests.exceptions.RequestException:
        raise requests.exceptions.RequestException(
            "There was an unexpected error with the request."
        )
    r_contents = rawcat.content.decode()  # convert from bytes to a String
    rstr = r_contents.split("\r\n")
    # remove initial line describing the number of sources returned
    # CRITICAL to proper interpretation of CSV data
    del rstr[0]

    return Table.read(rstr, format="csv")