from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_GET, require_POST

from .models import Package
from .util import load_tenant, return_jsonresponse, parse_jsonrequest

@require_GET
@load_tenant
def index(*_):
    # The sole motivation for this view is that we want to be able to
    # reverse('winget:index') in instructions for setting up the winget source.
    return HttpResponse("Please log in at /admin for instructions.")

@require_GET
@load_tenant
@return_jsonresponse
def information(*_):
    return {
        'SourceIdentifier': 'api.winget.pro',
        'ServerSupportedVersions': ['1.4.0', '1.5.0']
    }

@require_POST
@csrf_exempt
@load_tenant
@parse_jsonrequest
@return_jsonresponse
def manifestSearch(_, data, tenant):
    db_query = Q(tenant=tenant)
    if 'Query' in data:
        keyword = data['Query']['KeyWord']
        db_query &= Q(name__icontains=keyword)
    inclusions_query = Q()
    for inclusion in data.get('Inclusions', []):
        field = inclusion['PackageMatchField']
        if field == 'PackageName':
            keyword = inclusion['RequestMatch']['KeyWord']
            inclusions_query |= Q(name__icontains=keyword)
        elif field == 'ProductCode':
            keyword = inclusion['RequestMatch']['KeyWord']
            # We don't have a ProductCode. Use the identifier instead.
            inclusions_query |= Q(identifier__icontains=keyword)
        elif field == 'PackageFamilyName':
            keyword = inclusion['RequestMatch']['KeyWord']
            # We don't have family name. Use the name instead.
            inclusions_query |= Q(name__icontains=keyword)
    db_query &= inclusions_query
    for filter_ in data.get('Filters', []):
        field = filter_['PackageMatchField']
        keyword = filter_['RequestMatch']['KeyWord']
        if field == 'PackageIdentifier':
            db_query &= Q(identifier__icontains=keyword)
    return [
        {
            'PackageIdentifier': package.identifier,
            'PackageName': package.name,
            'Publisher': package.publisher,
            'Versions': [
                {'PackageVersion': version.version}
                for version in package.version_set.all()
            ]
        }
        for package in Package.objects.filter(db_query)
        if package.version_set.exists()
    ]

@require_GET
@load_tenant
def packageManifests(request, tenant, identifier):
    try:
        package = Package.objects.get(tenant=tenant, identifier=identifier)
    except ObjectDoesNotExist:
        # This is a peculiarity / inconsistency of the winget client. The API
        # design docs say that packageManifests should return 404 when a package
        # does not exist. But winget doesn't gracefully handle this case.
        # Instead, it expects HTTP 204.
        # See: https://github.com/microsoft/winget-cli-restsource/issues/170
        return HttpResponse(status=204)
    return _packageManifests(request, package)

@return_jsonresponse
def _packageManifests(request, package):
    result = {
        'PackageIdentifier': package.identifier
    }
    for version in package.version_set.all():
        installers = []
        for installer in version.installer_set.all():
            for scope in installer.scopes:
                installer_json = {
                    'Architecture': installer.architecture,
                    'InstallerType': installer.type,
                    'InstallerUrl':
                        request.build_absolute_uri(installer.file.url),
                    'InstallerSha256': installer.sha256,
                    'Scope': scope
                }
                if installer.nested_installer:
                    # NestedInstallerFiles needs to be a list even though at
                    # least winget 1.6.2771 does not support more than one.
                    installer_json['NestedInstallerFiles'] = [{
                        'RelativeFilePath': installer.nested_installer
                    }]
                if installer.nested_installer_type:
                    installer_json['NestedInstallerType'] = \
                        installer.nested_installer_type
                switches = {
                    'Silent': installer.silent_switch,
                    'SilentWithProgress': installer.silent_progress_switch,
                    'Interactive': installer.interactive_switch,
                    'InstallLocation': installer.install_location_switch,
                    'Log': installer.log_switch,
                    'Upgrade': installer.upgrade_switch,
                    'Custom': installer.custom_switch
                }
                nonempty_switches = {k: v for k, v in switches.items() if v}
                if nonempty_switches:
                    installer_json['InstallerSwitches'] = nonempty_switches
                installers.append(installer_json)
        version_json = {
            'PackageVersion': version.version,
            'DefaultLocale': {
                'PackageLocale': 'en-us',
                'Publisher': package.publisher,
                'PackageName': package.name,
                'ShortDescription': package.description
            },
            'Installers': installers
        }
        result.setdefault('Versions', []).append(version_json)
    return result