from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.core.exceptions import PermissionDenied
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings

import os
import pickle as pickle
import json

from pgweb.util.decorators import nocache
from pgweb.util.contexts import render_pgweb
from pgweb.util.helpers import PgXmlHelper, HttpServerError
from pgweb.util.misc import varnish_purge, version_sort

from pgweb.core.models import Version
from .models import Category, Product, StackBuilderApp


#######
# FTP browser
#######
def ftpbrowser(request, subpath):
    if subpath:
        # An actual path has been selected. Fancy!

        if subpath.find('..') > -1:
            # Just claim it doesn't exist if the user tries to do this
            # type of bad thing
            raise Http404
        subpath = subpath.strip('/')
    else:
        subpath = ""

    # Pickle up the list of things we need
    try:
        f = open(settings.FTP_PICKLE, "rb")
        allnodes = pickle.load(f)
        f.close()
    except Exception as e:
        return HttpServerError(request, "Failed to load ftp site information: %s" % e)

    # An incoming subpath may either be canonical, or have one or more elements
    # present that are actually symlinks. For each element of the path, test to
    # see if it is present in the pickle. If not, look for a symlink entry with
    # and if present, replace the original entry with the symlink target.
    canonpath = ''
    if subpath != '':
        parent = ''
        for d in subpath.split('/'):
            # Check if allnodes contains a node matching the path
            if d in allnodes[parent]:
                if allnodes[parent][d]['t'] == 'd':
                    canonpath = os.path.join(canonpath, d)
                elif allnodes[parent][d]['t'] == 'l':
                    canonpath = os.path.join(canonpath, allnodes[parent][d]['d']).strip('/')
                else:
                    # There's a matching node, but it's not a link or a directory
                    raise Http404

                parent = canonpath
            else:
                # There's no matching node
                raise Http404

    # If we wound up with a canonical path that doesn't match the original request,
    # redirect the user
    canonpath = canonpath.strip('/')
    if subpath != canonpath:
        return HttpResponseRedirect('/ftp/' + canonpath)

    node = allnodes[subpath]
    del allnodes

    # Add all directories
    directories = [{'link': k, 'url': k, 'type': 'd'} for k, v in list(node.items()) if v['t'] == 'd']
    # Add all symlinks (only directories supported)
    directories.extend([{'link': k, 'url': v['d'], 'type': 'l'} for k, v in list(node.items()) if v['t'] == 'l'])

    # A little early sorting wouldn't go amiss, so .. ends up at the top
    directories.sort(key=version_sort, reverse=True)

    # Add a link to the parent directory
    if subpath:
        directories.insert(0, {'link': '[Parent Directory]', 'url': '..'})

    # Fetch files
    files = [{'name': k, 'mtime': v['d'], 'size': v['s']} for k, v in list(node.items()) if v['t'] == 'f']

    breadcrumbs = []
    if subpath:
        breadroot = ""
        for pathpiece in subpath.split('/'):
            if not pathpiece:
                # Trailing slash will give out an empty pathpiece
                continue
            if breadroot:
                breadroot = "%s/%s" % (breadroot, pathpiece)
            else:
                breadroot = pathpiece
            breadcrumbs.append({'name': pathpiece, 'path': breadroot})

    # Check if there are any "content files" we should render directly on the webpage
    file_readme = ('README' in node and node['README']['t'] == 'f') and node['README']['c'] or None
    file_message = ('.message' in node and node['.message']['t'] == 'f') and node['.message']['c'] or None
    file_maintainer = ('CURRENT_MAINTAINER' in node and node['CURRENT_MAINTAINER']['t'] == 'f') and node['CURRENT_MAINTAINER']['c'] or None

    del node

    return render_pgweb(request, 'download', 'downloads/ftpbrowser.html', {
        'basepath': subpath.rstrip('/'),
        'directories': directories,
        'files': sorted(files, key=lambda f: f['name']),
        'breadcrumbs': breadcrumbs,
        'readme': file_readme,
        'messagefile': file_message,
        'maintainer': file_maintainer,
    })


# Accept an upload of the ftpsite pickle. This is fairly resource consuming,
# and not very optimized, but that's why we limit it so that only the ftp
# server(s) can post it.
# There is no concurrency check - the ftp site better not send more than one
# file in parallel.
@csrf_exempt
def uploadftp(request):
    if request.method != 'PUT':
        raise PermissionDenied("Invalid method")
    if not request.META['REMOTE_ADDR'] in settings.FTP_MASTERS:
        raise PermissionDenied("Invalid client address")
    # We have the data in request.body. Attempt to load it as
    # a pickle to make sure it's properly formatted
    pickle.loads(request.body)

    # Next, check if it's the same as the current file
    f = open(settings.FTP_PICKLE, "rb")
    x = f.read()
    f.close()
    if x == request.body:
        # Don't rewrite the file or purge any data if nothing changed
        return HttpResponse("NOT CHANGED", content_type="text/plain")

    # File has changed - let's write it!
    f = open("%s.new" % settings.FTP_PICKLE, "wb")
    f.write(request.body)
    f.close()
    os.rename("%s.new" % settings.FTP_PICKLE, settings.FTP_PICKLE)

    # Purge it out of varnish so we start responding right away
    varnish_purge("/ftp")

    # Finally, indicate to the client that we're happy
    return HttpResponse("OK", content_type="text/plain")


@csrf_exempt
def uploadyum(request):
    if request.method != 'PUT':
        raise PermissionDenied("Invalid method")
    if not request.META['REMOTE_ADDR'] in settings.FTP_MASTERS:
        raise PermissionDenied("Invalid client address")
    # We have the data in request.body. Attempt to load it as
    # json to ensure correct format.
    json.loads(request.body.decode('utf8'))

    # Next, check if it's the same as the current file
    if os.path.isfile(settings.YUM_JSON):
        with open(settings.YUM_JSON, "r") as f:
            if f.read() == request.body:
                # Don't rewrite the file or purge any data if nothing changed
                return HttpResponse("NOT CHANGED", content_type="text/plain")

    # File has changed - let's write it!
    with open("%s.new" % settings.YUM_JSON, "w") as f:
        f.write(request.body.decode('utf8'))

    os.rename("%s.new" % settings.YUM_JSON, settings.YUM_JSON)

    # Purge it out of varnish so we start responding right away
    varnish_purge("/download/js/yum.js")

    # Finally, indicate to the client that we're happy
    return HttpResponse("OK", content_type="text/plain")


@nocache
def mirrorselect(request, path):
    # Old access to mirrors will just redirect to the main ftp site.
    # We don't really need it anymore, but the cost of keeping it is
    # very low...
    return HttpResponseRedirect("https://ftp.postgresql.org/pub/%s" % path)


# Render javascript for yum downloads
def yum_js(request):
    with open(settings.YUM_JSON) as f:
        jsonstr = f.read()
    return render(request, 'downloads/js/yum.js', {
        'json': jsonstr,
        'supported_versions': ','.join([str(v.numtree) for v in Version.objects.filter(supported=True)]),
    }, content_type='application/json')


#######
# Product catalogue
#######
def categorylist(request):
    categories = Category.objects.all()
    return render_pgweb(request, 'download', 'downloads/categorylist.html', {
        'categories': categories,
    })


def productlist(request, catid, junk=None):
    category = get_object_or_404(Category, pk=catid)
    products = Product.objects.select_related('org', 'licencetype').filter(category=category, approved=True)
    return render_pgweb(request, 'download', 'downloads/productlist.html', {
        'category': category,
        'products': products,
        'productcount': len(products),
    })


#######
# Stackbuilder
#######
def applications_v2_xml(request):
    all_apps = StackBuilderApp.objects.select_related().filter(active=True)

    resp = HttpResponse(content_type='text/xml')
    x = PgXmlHelper(resp, skipempty=True)
    x.startDocument()
    x.startElement('applications', {})
    for a in all_apps:
        x.startElement('application', {})
        x.add_xml_element('id', a.textid)
        x.add_xml_element('platform', a.platform)
        x.add_xml_element('secondaryplatform', a.secondaryplatform)
        x.add_xml_element('version', a.version)
        x.add_xml_element('name', a.name)
        x.add_xml_element('description', a.description)
        x.add_xml_element('category', a.category)
        x.add_xml_element('pgversion', a.pgversion)
        x.add_xml_element('edbversion', a.edbversion)
        x.add_xml_element('format', a.format)
        x.add_xml_element('installoptions', a.installoptions)
        x.add_xml_element('upgradeoptions', a.upgradeoptions)
        x.add_xml_element('checksum', a.checksum)
        x.add_xml_element('mirrorpath', a.mirrorpath)
        x.add_xml_element('alturl', a.alturl)
        x.add_xml_element('versionkey', a.versionkey)
        x.add_xml_element('manifesturl', a.manifesturl)
        for dep in a.txtdependencies.split(','):
            x.add_xml_element('dependency', dep)
        x.endElement('application')
    x.endElement('applications')
    x.endDocument()
    return resp
