#
# Copyright (c) Daniel Sheffield 2023
# All rights reserved
#
# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
from typing import Callable, Iterable
from urllib.parse import urlencode
from bottle import request, FormsDict, redirect
from psycopg import Connection
from psycopg.connection import TupleRow

from ..data.filter import get_filter, get_query_param
from . import BOOLEAN, PARAMS
from .Cache import Cache
from .hash_util import hash_to_hex, normalize_hex

def normalize_query(query: FormsDict, allow: Iterable[str] = None) -> str:
    if 'hash' in query and query.hash:
        _hex = normalize_hex(query.hash)
        return f'hash={_hex}'
    allow = allow or PARAMS
    param = get_filter(query, allow=allow)
    norm = urlencode([
        (
            k, get_query_param(*param[k])
        ) if k != 'organic' else (
            "organic", BOOLEAN[BOOLEAN.get(query.organic, None)]
        ) for k in sorted(param) if param[k]
    ])
    return norm if len(norm) < 2000 else f'hash={hash_to_hex(hash(norm))}'

def _normalize_decorator(func: Callable, allow=None):
    def wrap(*args, **kwargs):
        _, _, path, *_ = request.urlparts
        normalized = normalize_query(request.params, allow=allow)
        if request.query_string != normalized:
            return redirect(f'{path}?{normalized}', 307)
        
        _hash = request.params.hash
        key = _hash if _hash else request.query_string
        return func(key, request.forms if _hash else request.query, *args, **kwargs)
    return wrap


def normalize(*args, **kwargs):
    if not len(args):
        return lambda f: _normalize_decorator(f, **kwargs)
    
    return _normalize_decorator(*args)


def _poison_decorator(func: Callable, cache: Cache = None):
    def wrap(*args, **kwargs):
        if request.params.reload == 'true':
            normalized = normalize_query(request.params, allow=PARAMS)
            cache.remove(normalized)
        return func(cache, *args, **kwargs)
    return wrap


def poison(*args, cache=None, **kwargs):
    if not len(args):
        return lambda f: _poison_decorator(f, cache=cache, **kwargs)
    raise Exception("decorator argument required")


def _cursor_decorator(func: Callable, connection: Connection[TupleRow] = None):
    def wrap(*args, **kwargs):
        try:
            with connection.cursor() as cur:
                return func(cur, *args, **kwargs)
        finally:
            connection.commit()
    return wrap


def cursor(*args, **kwargs):
    if not len(args):
        return lambda f: _cursor_decorator(f, **kwargs)
    raise Exception("decorator argument required")
