from datetime import timedelta, datetime

import dateutil.parser
import pytz
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import PermissionDenied
from django.db.models import Q

from tracker.models import Donation, SpeedRun, Bid

_DEFAULT_DONATION_DELTA = timedelta(hours=3)

# There is a slight complication in how this works, in that we cannot use the 'limit' set-up as a general filter mechanism, so these methods return the actual result, rather than a filter object

def get_recent_donations(
    offset = default_time(query_offset)
    if donations is None:
        donations = Donation.objects.all()
    if delta:
        high_filter = donations.filter(timereceived__gte=offset - delta)
        high_filter = donations
    count = high_filter.count()
    if max_donations is not None and count > max_donations:
        donations = donations[:max_donations]
    elif min_donations is not None and count < min_donations:
        donations = donations[:min_donations]
        donations = high_filter
    return donations

_DEFAULT_RUN_DELTA = timedelta(hours=6)

def get_upcoming_runs(
    offset = default_time(query_offset)
    if runs is None:
        runs = SpeedRun.objects.all()
    if include_current:
        runs = runs.filter(endtime__gte=offset)
        runs = runs.filter(starttime__gte=offset)
    if delta:
        high_filter = runs.filter(endtime__lte=offset + delta)
        high_filter = runs
    count = high_filter.count()
    if max_runs is not None and count > max_runs:
        runs = runs[:max_runs]
    elif min_runs is not None and count < min_runs:
        runs = runs[:min_runs]
        runs = high_filter
    return runs

def get_future_runs(**kwargs):
    return get_upcoming_runs(include_current=False, **kwargs)

# TODO: why is this so complicated
def upcoming_bid_filter(**kwargs):
    runs = [
        for run in get_upcoming_runs(
            SpeedRun.objects.filter(Q(bids__state='OPENED')).distinct(), **kwargs
    return Q(speedrun__in=runs)

def get_upcoming_bids(**kwargs):
    return Bid.objects.filter(upcoming_bid_filter(**kwargs))

def future_bid_filter(**kwargs):
    return upcoming_bid_filter(include_current=False, **kwargs)

# Gets all of the current prizes that are possible right now (and also _specific_ to right now)
def concurrent_prizes_filter(runs):
    run_count = runs.count()
    if run_count == 0:
        return Q(id=None)
    start_time = runs[0].starttime
    end_time = runs.reverse()[0].endtime
    # TODO: with the other changes to the logic I'm not sure this is correct any more, but
    # it's only a rough guess so maybe it's ok - BC 12/2019
    # ----
    # yes, the filter query here is correct.  We want to get all unwon prizes that _start_ before the last run in the list _ends_, and likewise all prizes that _end_ after the first run in the list _starts_.
    return Q(prizewinner__isnull=True) & (
        Q(startrun__starttime__lte=end_time, endrun__endtime__gte=start_time)
        | Q(starttime__lte=end_time, endtime__gte=start_time)
        | Q(

def current_prizes_filter(query_offset=None):
    offset = default_time(query_offset)
    return Q(prizewinner__isnull=True) & (
        Q(startrun__starttime__lte=offset, endrun__endtime__gte=offset)
        | Q(starttime__lte=offset, endtime__gte=offset)
        | Q(

def upcoming_prizes_filter(**kwargs):
    runs = get_upcoming_runs(**kwargs)
    return concurrent_prizes_filter(runs)

def future_prizes_filter(**kwargs):
    return upcoming_prizes_filter(include_current=False, **kwargs)

def todraw_prizes_filter(query_offset=None):
    offset = default_time(query_offset)
    return Q(state='ACCEPTED') & (
        & (
            | Q(endtime__lte=offset)
            | (Q(endtime=None) & Q(endrun=None))

def apply_feed_filter(query, model, feed_name, params=None, user=None):
    params = params or {}
    noslice = canonical_bool(params.pop('noslice', False))
    user = user or AnonymousUser()
    if model == 'donation':
        query = donation_feed_filter(feed_name, noslice, params, query, user)
    elif model in ['bid', 'bidtarget', 'allbids']:
        query = bid_feed_filter(feed_name, noslice, params, query, user)
    elif model == 'run':
        query = run_feed_filter(feed_name, noslice, params, query)
    elif model == 'prize':
        query = prize_feed_filter(feed_name, noslice, params, query, user)
    elif model == 'event':
        query = event_feed_filter(feed_name, params, query)
    return query

def event_feed_filter(feed_name, params, query):
    if feed_name == 'future':
        offsettime = default_time(params.get('time', None))
        query = query.filter(datetime__gte=offsettime)
    return query

def run_feed_filter(feed_name, noslice, params, query):
    if feed_name == 'current':
        query = get_upcoming_runs(**feed_params(noslice, params, {'runs': query}))
    elif feed_name == 'future':
        query = get_future_runs(**feed_params(noslice, params, {'runs': query}))
    return query

def feed_params(noslice, params, init=None):
    call_params = init or {}
    if 'maxRuns' in params:
        call_params['max_runs'] = int(params['maxRuns'])
    if 'minRuns' in params:
        call_params['min_runs'] = int(params['minRuns'])
    if noslice:
        call_params['max_runs'] = None
        call_params['min_runs'] = None
    if 'delta' in params:
        call_params['delta'] = timedelta(minutes=int(params['delta']))
    if 'time' in params:
        call_params['query_offset'] = default_time(params['time'])
    return call_params

def bid_feed_filter(feed_name, noslice, params, query, user):
    if feed_name == 'all':
        if not user.has_perm('tracker.view_hidden'):
            raise PermissionDenied
        pass  # no filtering required
    elif feed_name == 'open':
        query = query.filter(state='OPENED')
    elif feed_name == 'closed':
        query = query.filter(state='CLOSED')
    elif feed_name == 'current':
        query = query.filter(state='OPENED').filter(
            upcoming_bid_filter(**feed_params(noslice, params))
    elif feed_name == 'future':
        query = query.filter(state='OPENED').filter(
            future_bid_filter(**feed_params(noslice, params))
    elif feed_name == 'pending':
        if not user.has_perm('tracker.view_hidden'):
            raise PermissionDenied
        query = query.filter(state='PENDING')
    elif feed_name is None:
        query = query.filter(state__in=['OPENED', 'CLOSED'])
        raise ValueError(f'Unknown feed name `{feed_name}`')
    return query

def donation_feed_filter(feed_name, noslice, params, query, user):
    if (
        feed_name not in ['recent', 'toprocess', 'toread', 'all']
        and feed_name is not None
        raise ValueError(f'Unknown feed name `{feed_name}`')
    if feed_name == 'recent':
        query = get_recent_donations(
            **feed_params(noslice, params, {'donations': query})
    elif feed_name == 'toprocess':
        if not user.has_perm('tracker.view_comments'):
            raise PermissionDenied
        query = query.filter((Q(commentstate='PENDING') | Q(readstate='PENDING')))
    elif feed_name == 'toread':
        query = query.filter(Q(readstate='READY'))
    if feed_name != 'all':
        query = query.filter(transactionstate='COMPLETED', testdonation=False)
    elif not user.has_perm('tracker.view_pending'):
        raise PermissionDenied
    return query

def prize_feed_filter(feed_name, noslice, params, query, user):
    if feed_name == 'current':
        call_params = {}
        if 'time' in params:
            call_params['query_offset'] = default_time(params['time'])
        query = query.filter(current_prizes_filter(**call_params))
    elif feed_name == 'future':
        query = query.filter(upcoming_prizes_filter(**feed_params(noslice, params)))
    elif feed_name == 'won':
        # TODO: are these used? doesn't seem to take multi-prizes into account
        query = query.filter(Q(prizewinner__isnull=False))
    elif feed_name == 'unwon':
        query = query.filter(Q(prizewinner__isnull=True))
    elif feed_name == 'todraw':
        query = query.filter(todraw_prizes_filter())
    if feed_name != 'all':
        query = query.filter(state='ACCEPTED')
    elif not user.has_perm('tracker.change_prize'):
        raise PermissionDenied
    return query

def canonical_bool(b):
    if isinstance(b, str):
        if b.lower() in ['t', 'True', 'true', 'y', 'yes']:
            b = True
        elif b.lower() in ['f', 'False', 'false', 'n', 'no']:
            b = False
            b = None
    return b

def default_time(time):
    if time is None:
        time =
    elif isinstance(time, str):
        time = dateutil.parser.parse(time)
    return time.astimezone(pytz.utc)