from django.conf import settings
from django import forms
from importlib import import_module
from datetime import datetime
from dataclasses import dataclass
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Layout
import json
from abc import ABC, abstractmethod

from tom_alerts.models import BrokerQuery
from tom_targets.models import Target


def get_service_classes():
    Gets the broker classes available to this TOM as specified by ``TOM_ALERT_CLASSES`` in ````. If none are
    specified, returns the default set.

    :returns: dict of broker classes, with keys being the name of the broker and values being the broker class
    :rtype: dict
    except AttributeError:

    service_choices = {}
    for service in TOM_ALERT_CLASSES:
        mod_name, class_name = service.rsplit('.', 1)
            mod = import_module(mod_name)
            clazz = getattr(mod, class_name)
        except (ImportError, AttributeError):
            raise ImportError(f'Could not import {service}. Did you provide the correct path?')
        service_choices[] = clazz
    return service_choices

def get_service_class(name):
    Gets the specific broker class for a given broker name.

    :returns: Broker class
    :rtype: class
    available_classes = get_service_classes()
        return available_classes[name]
    except KeyError:
        raise ImportError(
            '''Could not a find a broker with that name.
            Did you add it to TOM_ALERT_CLASSES?'''

class GenericAlert:
    dataclass representing an alert in order to display it in the UI.

    timestamp: datetime
    id: int
    name: str
    ra: float
    dec: float
    mag: float
    score: float
    url: str

    def to_target(self):
        Returns a Target instance for an object defined by an alert.

        :returns: representation of object for an alert
        :rtype: `Target`
        return Target(

class GenericQueryForm(forms.Form):
    Form class representing the default form for a broker.

    query_name = forms.CharField(required=True)
    broker = forms.CharField(

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.add_input(Submit('submit', 'Submit'))
        self.common_layout = Layout('query_name', 'broker')

    def serialize_parameters(self):
        Returns a JSON-serialized representation of the form data.

        :returns: JSON-ified form parameters
        :rtype: str
        return json.dumps(self.cleaned_data)

    def save(self, query_id=None):
        Saves the form data in the database as a ``BrokerQuery``.

        :returns: ``BrokerQuery`` model representation of the form that was saved to the db
        :rtype: ``BrokerQuery``
        if query_id:
            query = BrokerQuery.objects.get(id=query_id)
            query = BrokerQuery() = self.cleaned_data['query_name'] = self.cleaned_data['broker']
        query.parameters = self.serialize_parameters()
        return query

class GenericBroker(ABC):
    The ``GenericBroker`` provides an interface for implementing a broker module. It contains a number of methods to be
    implemented, but only the methods decorated with ``@abstractmethod`` are required to be implemented. In order to
    make use of a broker module, add the path to ``TOM_ALERT_CLASSES`` in your ````.

    For an implementation example, please see

    def fetch_alerts(self, parameters):
        This method takes in the query parameters needed to filter
        alerts for a broker and makes the GET query to the broker

        :param parameters: JSON string of query parameters
        :type parameters: str

    def fetch_alert(self, id):
        This method takes an alert id and retrieves the specific
        alert data from the given broker.

        :param id: Broker-specific id corresponding with the desired alert
        :type id: str

    def process_reduced_data(self, target, alert=None):
        Retrieves and creates records for any reduced data provided
        by a specific broker. Updates existing data if it has changed.

        :param target: ``Target`` object that was previously created from a ``BrokerQuery`` alert
        :type target: Target

        :param alert: alert data from a particular ``BrokerQuery``
        :type alert: str

    def to_target(self, alert):
        Creates ``Target`` object from the broker-specific alert data.

        :param alert: alert data from a particular ``BrokerQuery``
        :type alert: str

    def to_generic_alert(self, alert):
        This method creates a ``GenericAlert`` object from the broker-specific
        alert data for use outside of the implementation of the ``GenericBroker``.

        :param alert: alert data from a particular ``BrokerQuery``
        :type alert: str

    def fetch_and_save_all(self, parameters):
        Gets all alerts using a particular ``BrokerQuery`` and creates a ``Target`` from each one.

        :param parameters: JSON string of query parameters
        :type parameters: str

        :returns: list of ``Target`` objects
        :rtype: list
        targets = []
        for alert in self.fetch_alerts(parameters):
            generic_alert = self.to_generic_alert(alert)
            full_alert = self.fetch_alert(
            target = self.to_target(full_alert)

        return targets