from celery import shared_task
from functools import reduce
import json
from operator import or_
from urllib import parse

from django.conf import settings
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.db.models import Q
from django.forms.models import model_to_dict
from django.http import FileResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse

from shared.request import make_response, ErrorResponse

from .models import Proposal, Attribute, Document, Event, Image, Layer
from .query import build_proposal_query, build_event_query
from utils import bounds_from_box, add_params

default_attributes = [
    "applicant_name", "legal_notice"
]


def proposal_json(proposal,
                  include_images=True,
                  include_attributes=default_attributes,
                  include_events=False,
                  include_documents=True,
                  include_projects=True):
    pdict = model_to_dict(proposal, exclude=["location", "fulltext"])
    pdict["location"] = {
        "lat": proposal.location.y,
        "lng": proposal.location.x
    }

    if include_documents:
        pdict["documents"] = [d.to_dict() for d in proposal.documents.all()]

    if include_images:
        images = proposal.images.order_by("-priority")
        # Booleans are considered integers
        if isinstance(include_images, int) and include_images is not True:
            images = images[0:include_images]

        pdict["images"] = [img.to_dict() for img in images]

    if include_attributes:
        attributes = proposal.attributes.filter(hidden=False)
        if include_attributes is not True:
            attributes = attributes.filter(handle__in=include_attributes)
        pdict["attributes"] = [a.to_dict() for a in attributes]

    if include_events:
        pdict["events"] = [e.to_json_dict() for e in proposal.events.all()]

    if include_projects and proposal.project:
        pdict["project"] = proposal.project.to_dict()

    # TODO: Filter on parcel attributes

    # TODO: fulltext query

    return pdict


def layer_json(layer):
    return {"name": layer.name,
            "icon_text": layer.icon_text,
            "icon": layer.icon.url,
            "url": layer.url,
            "region_name": layer.region_name}


def _query(req):
    queries = req.GET.getlist("query")
    if queries:
        query = reduce(or_, (build_proposal_query(json.loads(q))
                             for q in queries), Q())
    else:
        query = build_proposal_query(req.GET)
    proposals = Proposal.objects.filter(query)
    if "include_projects" in req.GET:
        proposals = proposals.select_related("project")
    return proposals


def paginator_context(page, page_url=lambda page: f"?page={page}"):
    next_page = page.has_next() and page.next_page_number()
    prev_page = page.has_previous() and page.previous_page_number()
    return {
        "count": page.paginator.count,
        "total_pages": page.paginator.num_pages,
        "page": page.number,
        "next_page": next_page,
        "next_url": next_page and page_url(next_page),
        "prev_page": "",
        "prev_url": prev_page and page_url(prev_page),
        "per_page": page.paginator.per_page
    }


# Views:
@make_response("list.djhtml")
def list_proposals(req):
    proposals = _query(req)

    try:
        page = int(req.GET["page"])
    except (ValueError, KeyError):
        page = 1

    context = {}

    if page:
        try:
            per_page = int(req.GET.get("per_page", 50))
        except ValueError:
            per_page = 50
        paginator = Paginator(proposals, per_page=min(per_page, 50))
        proposals = paginator.page(page)
        try:
            query_params = req.GET.copy()
            def make_url(page):
                query_params["page"] = str(page)
                return req.path + "?" + query_params.urlencode()

            context["paginator"] = paginator_context(proposals, make_url)
        except (PageNotAnInteger, EmptyPage) as err:
            raise ErrorResponse("No such page", {"page": page}, err=err)

    context["proposals"] = [
        proposal_json(
            proposal, include_images=1, include_events=True) for proposal in proposals
    ]

    return context


@make_response("view.djhtml")
def view_proposal(req, pk=None):
    pk = req.GET.get("pk", pk)
    lookup = req.site_config.query_defaults.copy()
    if pk:
        lookup["pk"] = pk
    else:
        if "case_number" in req.GET:
            lookup["case_number"] = req.GET["case_number"]

    proposal = get_object_or_404(Proposal, **lookup)

    return proposal_json(
        proposal,
        include_attributes=True,
        include_images=True,
        include_events=True)


# Document views
@make_response()
def view_document(req, pk):
    "Retrieve details about a Document."
    doc = get_object_or_404(Document, pk=pk)

    return doc.to_dict()


def download_document(req, pk):
    doc = get_object_or_404(Document, pk=pk)

    if not doc.document:
        return {}

    if settings.IS_PRODUCTION:
        # Serve the file using mod_xsendfile
        pass

    return FileResponse(doc.document)


@make_response("layers.djhtml")
def list_layers(req):
    query = {}
    if req.GET.get("box"):
        query["envelope__overlaps"] = bounds_from_box(req.GET.get("box"))
    regions = req.GET.getlist("region_name")
    if regions:
        query["region_name__in"] = regions
    layers = Layer.objects.filter(**query)
    return {"layers": [layer_json(l) for l in layers]}


@make_response("events.djhtml")
def list_events(req):
    query = build_event_query(req.GET)
    events = Event.objects.filter(query).order_by("date")
    title = "Events" if query else "Upcoming Events"

    return {"events": [event.to_json_dict() for event in events],
            "title": title}


@make_response("event.djhtml")
def view_event(req, pk=None):
    if not pk:
        pk = req.GET.get("pk")

    event = get_object_or_404(Event, pk=pk)
    d = event.to_json_dict()
    d["proposals"] = [
        proposal_json(
            p,
            include_images=False,
            include_attributes=["applicant_name", "legal_notice"],
            include_documents=False)
        for p in event.proposals.all().select_related("project")
    ]
    return {"event": d}


@make_response()
def view_image(req, pk=None):
    if not pk:
        pk = req.GET.get("pk")

    image = get_object_or_404(Image, pk=pk)
    return image.to_dict()

@make_response("images.djhtml")
def list_images(req):
    return {"images": Image.objects.exclude(image="")}