import json
import re
import datetime

from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template import RequestContext
try:
    from mongoengine.base import ValidationError
except ImportError:
    from mongoengine.errors import ValidationError

from crits.core import form_consts
from crits.core.class_mapper import class_from_id, class_from_value
from crits.core.crits_mongoengine import EmbeddedSource, EmbeddedCampaign
from crits.core.crits_mongoengine import json_handler, create_embedded_source
from crits.core.handsontable_tools import convert_handsontable_to_rows, parse_bulk_upload
from crits.core.handlers import build_jtable, jtable_ajax_list, jtable_ajax_delete
from crits.core.data_tools import convert_string_to_bool
from crits.core.handlers import csv_export
from crits.core.user_tools import user_sources, is_user_favorite
from crits.core.user_tools import is_user_subscribed
from crits.domains.domain import Domain, TLD
from crits.domains.forms import AddDomainForm
from crits.ips.ip import IP
from crits.ips.handlers import validate_and_normalize_ip
from crits.notifications.handlers import remove_user_from_notification
from crits.objects.handlers import object_array_to_dict, validate_and_add_new_handler_object
from crits.relationships.handlers import forge_relationship
from crits.services.handlers import run_triage, get_supported_services

from crits.vocabulary.relationships import RelationshipTypes

def get_valid_root_domain(domain):
    """
    Validate the given domain and TLD, and if valid, parse out the root domain

    :param domain: the domain to validate and parse
    :type domain: str
    :returns: tuple: (Valid root domain, Valid FQDN, Error message)
    """

    root = fqdn = error = ""
    black_list = "/:@\ "
    domain = domain.strip()

    if any(c in black_list for c in domain):
        error = 'Domain cannot contain space or characters %s' % (black_list)
    else:
        global tld_parser
        root = tld_parser.parse(domain)
        if root == "no_tld_found_error":
            tld_parser = etld()
            root = tld_parser.parse(domain)
            if root == "no_tld_found_error":
                error = 'No valid TLD found'
                root = ""
        else:
            fqdn = domain.lower()

    return (root, fqdn, error)

def get_domain_details(domain, analyst):
    """
    Generate the data to render the Domain details template.

    :param domain: The name of the Domain to get details for.
    :type domain: str
    :param analyst: The user requesting this information.
    :type analyst: str
    :returns: template (str), arguments (dict)
    """

    template = None
    allowed_sources = user_sources(analyst)
    dmain = Domain.objects(domain=domain,
                           source__name__in=allowed_sources).first()
    if not dmain:
        error = ("Either no data exists for this domain"
                 " or you do not have permission to view it.")
        template = "error.html"
        args = {'error': error}
        return template, args

    dmain.sanitize_sources(username="%s" % analyst,
                           sources=allowed_sources)

    # remove pending notifications for user
    remove_user_from_notification("%s" % analyst, dmain.id, 'Domain')

    # subscription
    subscription = {
            'type': 'Domain',
            'id': dmain.id,
            'subscribed': is_user_subscribed("%s" % analyst,
                                             'Domain',
                                             dmain.id),
    }

    #objects
    objects = dmain.sort_objects()

    #relationships
    relationships = dmain.sort_relationships("%s" % analyst, meta=True)

    # relationship
    relationship = {
            'type': 'Domain',
            'value': dmain.id
    }

    #comments
    comments = {'comments': dmain.get_comments(),
                'url_key':dmain.domain}

    #screenshots
    screenshots = dmain.get_screenshots(analyst)

    # favorites
    favorite = is_user_favorite("%s" % analyst, 'Domain', dmain.id)

    # services
    service_list = get_supported_services('Domain')

    # analysis results
    service_results = dmain.get_analysis_results()

    args = {'objects': objects,
            'relationships': relationships,
            'comments': comments,
            'favorite': favorite,
            'relationship': relationship,
            'subscription': subscription,
            'screenshots': screenshots,
            'domain': dmain,
            'service_list': service_list,
            'service_results': service_results}

    return template, args

def generate_domain_csv(request):
    """
    Generate a CSV file of the Domain information

    :param request: The request for this CSV.
    :type request: :class:`django.http.HttpRequest`
    :returns: :class:`django.http.HttpResponse`
    """

    response = csv_export(request,Domain)
    return response

def generate_domain_jtable(request, option):
    """
    Generate the jtable data for rendering in the list template.

    :param request: The request for this jtable.
    :type request: :class:`django.http.HttpRequest`
    :param option: Action to take.
    :type option: str of either 'jtlist', 'jtdelete', or 'inline'.
    :returns: :class:`django.http.HttpResponse`
    """

    obj_type = Domain
    type_ = "domain"
    mapper = obj_type._meta['jtable_opts']
    if option == "jtlist":
        # Sets display url
        details_url = mapper['details_url']
        details_url_key = mapper['details_url_key']
        fields = mapper['fields']
        response = jtable_ajax_list(obj_type,
                                    details_url,
                                    details_url_key,
                                    request,
                                    includes=fields)
        return HttpResponse(json.dumps(response,
                                       default=json_handler),
                            content_type="application/json")
    if option == "jtdelete":
        response = {"Result": "ERROR"}
        if jtable_ajax_delete(obj_type,request):
            response = {"Result": "OK"}
        return HttpResponse(json.dumps(response,
                                       default=json_handler),
                            content_type="application/json")
    jtopts = {
        'title': "Domains",
        'default_sort': mapper['default_sort'],
        'listurl': reverse('crits.%ss.views.%ss_listing' % (type_, type_),
                                                    args=('jtlist',)),
        'deleteurl': reverse('crits.%ss.views.%ss_listing' % (type_, type_),
                                                    args=('jtdelete',)),
        'searchurl': reverse(mapper['searchurl']),
        'fields': mapper['jtopts_fields'],
        'hidden_fields': mapper['hidden_fields'],
        'linked_fields': mapper['linked_fields'],
        'details_link': mapper['details_link']
    }
    jtable = build_jtable(jtopts,request)
    jtable['toolbar'] = [
        {
            'tooltip': "'All Domains'",
            'text': "'All'",
            'click': "function () {$('#domain_listing').jtable('load', {'refresh': 'yes'});}",
            'cssClass': "'jtable-toolbar-center'",
        },
        {
            'tooltip': "'New Domains'",
            'text': "'New'",
            'click': "function () {$('#domain_listing').jtable('load', {'refresh': 'yes', 'status': 'New'});}",
            'cssClass': "'jtable-toolbar-center'",
        },
        {
            'tooltip': "'In Progress Domains'",
            'text': "'In Progress'",
            'click': "function () {$('#domain_listing').jtable('load', {'refresh': 'yes', 'status': 'In Progress'});}",
            'cssClass': "'jtable-toolbar-center'",
        },
        {
            'tooltip': "'Analyzed Domains'",
            'text': "'Analyzed'",
            'click': "function () {$('#domain_listing').jtable('load', {'refresh': 'yes', 'status': 'Analyzed'});}",
            'cssClass': "'jtable-toolbar-center'",
        },
        {
            'tooltip': "'Deprecated Domains'",
            'text': "'Deprecated'",
            'click': "function () {$('#domain_listing').jtable('load', {'refresh': 'yes', 'status': 'Deprecated'});}",
            'cssClass': "'jtable-toolbar-center'",
        },
        {
            'tooltip': "'Add Domain'",
            'text': "'Add Domain'",
            'click': "function () {$('#new-domain').click()}",
        },
    ]
    if option == "inline":
        return render_to_response("jtable.html",
                                  {'jtable': jtable,
                                   'jtid': '%s_listing' % type_,
                                   'button' : '%ss_tab' % type_},
                                  RequestContext(request))
    else:
        return render_to_response("%s_listing.html" % type_,
                                  {'jtable': jtable,
                                   'jtid': '%s_listing' % type_},
                                  RequestContext(request))

def add_new_domain_via_bulk(data, rowData, request, errors,
                            is_validate_only=False, cache={}):
    """
    Wrapper for add_new_domain to pass in rowData.

    :param data: The data about the domain.
    :type data: dict
    :param rowData: Any objects that need to be added to the domain.
    :type rowData: dict
    :param request: The Django request.
    :type request: :class:`django.http.HttpRequest`
    :param errors: A list of current errors to append to.
    :type errors: list
    :param is_validate_only: Only validate the data and return any errors.
    :type is_validate_only: boolean
    :param cache: Cached data, typically for performance enhancements
                  during bulk uperations.
    :type cache: dict
    :returns: tuple
    """

    return add_new_domain(data, request, errors, rowData=rowData,
                          is_validate_only=is_validate_only, cache=cache)

def retrieve_domain(domain, cache):
    """
    Retrieves a domain by checking cache first. If not in cache
    then queries mongo for the domain.

    :param domain: The domain name.
    :type domain: str
    :param cache: Cached data, typically for performance enhancements
                  during bulk uperations.
    :type cache: dict
    :returns: :class:`crits.domains.domain.Domain`
    """
    domain_obj = None
    cached_results = cache.get(form_consts.Domain.CACHED_RESULTS)

    if cached_results: