#
# Copyright (c) Daniel Sheffield 2021 - 2023
#
# All rights reserved
#
# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
from collections import OrderedDict
from typing import Tuple
from psycopg.sql import (
    Identifier,
    SQL,
    Literal,
    Composable,
)
from .filter import (
    get_include_exclude,
)
from .util import(
    get_select,
    get_from,
    get_groupby,
    get_where_include_exclude,
)

def get_window(unit, organic):
    window = SQL(f"""(
PARTITION BY {'organic,' if organic is not None else ''} product_id
ORDER BY convert_unit(units.name, {{unit}}, products.name) NULLS FIRST, ts
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
)""").format(unit=Literal(unit))

    return window

def get_selectors(
    unit: str,
    organic: str
) -> OrderedDict[Tuple[str, Composable]]:
    window = get_window(unit, organic)
    return  OrderedDict([
        ('id', Identifier('transactions', 'id')),
        ('ts_raw', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
        ('%d/%m/%y %_I%P', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
        ('code', Identifier('stores', 'code')),
        ('$/unit', SQL("""TRUNC(
    price / quantity / convert_unit(units.name, {unit}, products.name), 4
    )""").format(unit=Literal(unit))),
        *[(
            k, v.format(unit=Literal(unit), window=window)
        ) for k, v in WINDOW.items()
        ],
        ('price', SQL("""TRUNC(price, 4)""")),
        ('quantity', SQL("""TRUNC(
    quantity * convert_unit(units.name, {unit}, products.name), 4
)""").format(unit=Literal(unit))),
        ('product', Identifier('products', 'name')),
        ('category', Identifier('categories', 'name')),
        ('group', Identifier('groups', 'name')),
        ('organic', Identifier('organic')),
        ('tags', SQL(
"""array_agg({tag_name}) FILTER (WHERE {tag_name} IS NOT NULL)"""
        ).format(
            tag_name=Identifier('tags','name'),
        ))
    ])

JOINS = OrderedDict([
    ('units', ('id', 'unit_id')),
    ('stores', ('id', 'store_id')),
    ('products', ('id', 'product_id')),
    ('categories', ('id', 'category_id')),
    ('groups', ('id', 'group_id')),
    ('tags_map', ('transaction_id', ('transactions','id'))),
    ('tags', ('id', ('tags_map','tag_id'))),
])

WINDOW = OrderedDict([
    ('last', SQL("""TRUNC(last_value(
    price / quantity / convert_unit(units.name, {unit}, products.name)
) OVER {window}, 4)
""")),
    ('avg', SQL("""TRUNC(sum(CASE
    WHEN convert_unit(units.name, {unit}, products.name) IS NOT NULL THEN price
    ELSE NULL
END) OVER {window} / sum(
    quantity * convert_unit(units.name, {unit}, products.name)
) OVER {window}, 4)
""")),
    ('min', SQL("""TRUNC(min(
    price / quantity / convert_unit(units.name, {unit}, products.name)
) OVER {window}, 4)
""")),
    ('max', SQL("""TRUNC(max(
    price / quantity / convert_unit(units.name, {unit}, products.name)
) OVER {window}, 4)
""")),
])

GROUPBY = OrderedDict([
    ('id', Identifier('transactions', 'id')),
    ('ts_raw', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
    ('%d/%m/%y %_I%P', SQL("""(transactions.ts AT TIME ZONE 'UTC')::timestamp without time zone""")),
    ('code', Identifier('stores', 'code')),
    ('$/unit', SQL("""
TRUNC(
    price / quantity / convert_unit(units.name, {unit}, products.name), 4
)""")),
    ('price', SQL("""TRUNC(price, 4)""")),
    ('quantity', SQL("""
TRUNC(
    quantity * convert_unit(units.name, {unit}, products.name), 4
)""")),
    ('group', Identifier('groups', 'name')),
    ('category', Identifier('categories', 'name')),
    ('product', Identifier('products', 'name')),
    ('organic', Identifier('organic')),
    ('unit', Identifier('units', 'name')),
])


def get_where(product=None, category=None, group=None, tag=None, organic=None, limit='90 days'):
    where = [
        get_where_include_exclude(
            k, 'name', *list(map(list, get_include_exclude(v)))
        ) for k, v in {
            'products': product,
            'categories': category,
            'groups': group,
            'tags': tag,
        }.items()
    ]
    if organic is not None:
        where.append(SQL(' ').join([
            Identifier('organic'),
            SQL('='),
            Literal(organic),
        ]))
    if limit:
        where.append(
            SQL("{ts} at time zone 'utc' BETWEEN now() - {interval} AND now()").format(
                ts=Identifier('ts'),
                interval=SQL("{literal}::interval").format(literal=Literal(limit))
            )
        )
    return SQL('').join([
        SQL("WHERE"
            "\n      "),
        SQL("\n  AND ").join(where),
    ]) if where else SQL('')


def get_historic_prices_statement(unit, sort=None, product=None, category=None, group=None, tag=None, organic=None, limit='90 days'):
    
    return SQL('\n').join([
        get_select(get_selectors(unit, organic)),
        get_from("transactions", JOINS),
        get_where(product=product, category=category, group=group, tag=tag, organic=organic, limit=limit),
        get_groupby(GROUPBY, lambda x: x.format(unit=Literal(unit))),
        get_sort(sort, organic),
    ])

def get_sort(sort, organic):
    organic_sort = f"{'organic,' if organic is not None else ''}"
    sort_sql = SQL('').join([
        SQL('{sort} {direction},').format(
            sort=Identifier(f'{sort}'),
            direction=SQL('DESC' if sort == 'ts' else 'ASC')
        ),
    ]) if sort is not None else SQL('')
    return SQL("""
ORDER BY {organic_sort} {sort} code, product, category, "group", "$/unit" ASC, ts DESC
""").format(
        sort=sort_sql,
        organic_sort=SQL(organic_sort),
    )
