from datetime import datetime
from typing import List

from flask import Blueprint, jsonify, request, json

from app.models.products import Product, Category, products_categories
from app import db

products_blueprint = Blueprint('products', __name__)


def create_or_get_categories(p: dict) -> List[Category]:
    """
    Func to get existing categories objects or create new otherwise
    :param p: payload of request
    :return: list of categories
    """
    recevied_categories: List[Category] = [Category(name=cat) for cat in p['categories']]
    categories = []
    for cat in recevied_categories:
        exists = db.session.query(db.exists().where(Category.name == cat.name)).all()[0][0]
        if exists:
            existing_category = Category.query.filter(Category.name == cat.name).all()[0]
            categories.append(existing_category)
        else:
            categories.append(cat)
    return categories


@products_blueprint.route('/products', methods=['GET'])
def get_products():
    return jsonify({
        'results': [p.serialized for p in Product.query.all()]
    })


@products_blueprint.route('/create_product', methods=['POST'])
def create_product():
    data = request.get_data().decode('utf-8')
    payload = json.loads(data)
    datetime_format = '%Y-%m-%d %H:%M:%S'

    if len(payload['categories']) < 1 or len(payload['categories']) > 5:
        return '{"status": "error", "msg": "categories number must be between 1 and 5"}', 400

    categories = create_or_get_categories(payload)

    try:
        new_prod = Product(name=payload['name'],
                           rating=float(payload['rating']),
                           featured=bool(payload['featured'] if 'featured' in payload.keys() else None),
                           expiration_date=(datetime.strptime(payload['expiration_date'], datetime_format)
                                            if ('expiration_date' in payload.keys()) else None),
                           brand_id=int(payload['brand_id']),
                           items_in_stock=int(payload['items_in_stock']),
                           receipt_date=(datetime.strptime(payload['receipt_date'], datetime_format)
                                         if ('receipt_date' in payload.keys()) else None))
    except TypeError as e:
        return '{"status": "error", "msg": "TypeError occured: check values of fields"}'
    except KeyError as e:
        return '{"status": "error", "msg": "field %s have not been found, but is required"}' % str(e), 400
    if new_prod.rating > 8.0:
        new_prod.featured = True
    [cat.products.append(new_prod) for cat in categories]
    [db.session.add(cat) for cat in categories]
    db.session.commit()
    return jsonify({"status": "ok", "msg": "product received"})


@products_blueprint.route('/update_product', methods=['PUT'])
def update_product():
    data = request.get_data().decode('utf-8')
    payload = json.loads(data)
    datetime_format = '%Y-%m-%d %H:%M:%S'

    product = Product.query.filter(Product.id == payload['id'])
    if product:
        if 'name' in payload.keys():
            product.update({'name': payload['name']})
        if 'featured' in payload.keys():
            product.update({'featured': bool(payload['featured'])})
        if 'rating' in payload.keys():
            product.update({'rating': float(payload['rating'])})
            if product.rating > 8.0:
                product.featured = True
        if 'items_in_stock' in payload.keys():
            product.update({'items_in_stock': int(payload['items_in_stock'])})
        if 'receipt_date' in payload.keys():
            product.update({'receipt_date': datetime.strptime(payload['receipt_date'], datetime_format)})
        if 'brand' in payload.keys():
            product.update({'brand': int(payload['brand'])})
        if 'categories' in payload.keys():
            categories = create_or_get_categories(payload)
            db.session.query(products_categories).filter(
                products_categories.c.product_id == int(payload['id'])).delete(synchronize_session=False)
            product_obj = product.all()[0]
            [cat.products.append(product_obj) for cat in categories]
            [db.session.add(cat) for cat in categories]
        if 'expiration_date' in payload.keys():
            product.update({'expiration_date': datetime.strptime(payload['expiration_date'], datetime_format)})

        db.session.commit()
        return jsonify({"status": "ok", "msg": "product updated"})
    else:
        return '{"status": "error", "msg": "no product found with given id"}', 404


@products_blueprint.route('/delete_product', methods=['DELETE'])
def delete_product():
    data = request.get_data().decode('utf-8')
    p = json.loads(data)

    products_result = Product.query.filter(Product.id == int(p['id'])).delete(synchronize_session=False)
    products_categories_result = db.session.query(products_categories).filter(
        products_categories.c.product_id == int(p['id'])).delete(synchronize_session=False)
    db.session.commit()
    if products_result == 1:
        return jsonify({"status": "ok",
                        "msg": "product deleted, also %d product_categories relations deleted"
                               % products_categories_result})
    else:
        return jsonify({"status": "warning", "msg": "%d products deleted, also %d product_categories relations deleted"
                                                    % (products_result, products_categories_result)})

[((9, 21, 9, 52), 'flask.Blueprint', 'Blueprint', ({(9, 31, 9, 41): '"""products"""', (9, 43, 9, 51): '__name__'}, {}), "('products', __name__)", False, 'from flask import Blueprint, jsonify, request, json\n'), ((40, 14, 40, 30), 'flask.json.loads', 'json.loads', ({(40, 25, 40, 29): 'data'}, {}), '(data)', False, 'from flask import Blueprint, jsonify, request, json\n'), ((66, 4, 66, 23), 'app.db.session.commit', 'db.session.commit', ({}, {}), '()', False, 'from app import db\n'), ((67, 11, 67, 63), 'flask.jsonify', 'jsonify', ({(67, 19, 67, 62): "{'status': 'ok', 'msg': 'product received'}"}, {}), "({'status': 'ok', 'msg': 'product received'})", False, 'from flask import Blueprint, jsonify, request, json\n'), ((73, 14, 73, 30), 'flask.json.loads', 'json.loads', ({(73, 25, 73, 29): 'data'}, {}), '(data)', False, 'from flask import Blueprint, jsonify, request, json\n'), ((76, 14, 76, 63), 'app.models.products.Product.query.filter', 'Product.query.filter', ({(76, 35, 76, 62): "Product.id == payload['id']"}, {}), "(Product.id == payload['id'])", False, 'from app.models.products import Product, Category, products_categories\n'), ((111, 8, 111, 24), 'flask.json.loads', 'json.loads', ({(111, 19, 111, 23): 'data'}, {}), '(data)', False, 'from flask import Blueprint, jsonify, request, json\n'), ((116, 4, 116, 23), 'app.db.session.commit', 'db.session.commit', ({}, {}), '()', False, 'from app import db\n'), ((18, 43, 18, 61), 'app.models.products.Category', 'Category', (), '', False, 'from app.models.products import Product, Category, products_categories\n'), ((65, 5, 65, 24), 'app.db.session.add', 'db.session.add', ({(65, 20, 65, 23): 'cat'}, {}), '(cat)', False, 'from app import db\n'), ((102, 8, 102, 27), 'app.db.session.commit', 'db.session.commit', ({}, {}), '()', False, 'from app import db\n'), ((103, 15, 103, 66), 'flask.jsonify', 'jsonify', ({(103, 23, 103, 65): "{'status': 'ok', 'msg': 'product updated'}"}, {}), "({'status': 'ok', 'msg': 'product updated'})", False, 'from flask import Blueprint, jsonify, request, json\n'), ((118, 15, 120, 61), 'flask.jsonify', 'jsonify', ({(118, 23, 120, 60): "{'status': 'ok', 'msg': \n    'product deleted, also %d product_categories relations deleted' %\n    products_categories_result}"}, {}), "({'status': 'ok', 'msg': \n    'product deleted, also %d product_categories relations deleted' %\n    products_categories_result})", False, 'from flask import Blueprint, jsonify, request, json\n'), ((122, 15, 123, 101), 'flask.jsonify', 'jsonify', ({(122, 23, 123, 100): "{'status': 'warning', 'msg': \n    '%d products deleted, also %d product_categories relations deleted' % (\n    products_result, products_categories_result)}"}, {}), "({'status': 'warning', 'msg': \n    '%d products deleted, also %d product_categories relations deleted' % (\n    products_result, products_categories_result)})", False, 'from flask import Blueprint, jsonify, request, json\n'), ((39, 11, 39, 29), 'flask.request.get_data', 'request.get_data', ({}, {}), '()', False, 'from flask import Blueprint, jsonify, request, json\n'), ((72, 11, 72, 29), 'flask.request.get_data', 'request.get_data', ({}, {}), '()', False, 'from flask import Blueprint, jsonify, request, json\n'), ((110, 11, 110, 29), 'flask.request.get_data', 'request.get_data', ({}, {}), '()', False, 'from flask import Blueprint, jsonify, request, json\n'), ((98, 13, 98, 32), 'app.db.session.add', 'db.session.add', ({(98, 28, 98, 31): 'cat'}, {}), '(cat)', False, 'from app import db\n'), ((33, 42, 33, 61), 'app.models.products.Product.query.all', 'Product.query.all', ({}, {}), '()', False, 'from app.models.products import Product, Category, products_categories\n'), ((52, 44, 52, 106), 'datetime.datetime.strptime', 'datetime.strptime', ({(52, 62, 52, 88): "payload['expiration_date']", (52, 90, 52, 105): 'datetime_format'}, {}), "(payload['expiration_date'], datetime_format)", False, 'from datetime import datetime\n'), ((56, 41, 56, 100), 'datetime.datetime.strptime', 'datetime.strptime', ({(56, 59, 56, 82): "payload['receipt_date']", (56, 84, 56, 99): 'datetime_format'}, {}), "(payload['receipt_date'], datetime_format)", False, 'from datetime import datetime\n'), ((89, 44, 89, 103), 'datetime.datetime.strptime', 'datetime.strptime', ({(89, 62, 89, 85): "payload['receipt_date']", (89, 87, 89, 102): 'datetime_format'}, {}), "(payload['receipt_date'], datetime_format)", False, 'from datetime import datetime\n'), ((100, 47, 100, 109), 'datetime.datetime.strptime', 'datetime.strptime', ({(100, 65, 100, 91): "payload['expiration_date']", (100, 93, 100, 108): 'datetime_format'}, {}), "(payload['expiration_date'], datetime_format)", False, 'from datetime import datetime\n'), ((114, 33, 114, 70), 'app.db.session.query', 'db.session.query', ({(114, 50, 114, 69): 'products_categories'}, {}), '(products_categories)', False, 'from app import db\n'), ((23, 32, 23, 80), 'app.models.products.Category.query.filter', 'Category.query.filter', ({(23, 54, 23, 79): '(Category.name == cat.name)'}, {}), '(Category.name == cat.name)', False, 'from app.models.products import Product, Category, products_categories\n'), ((94, 12, 94, 49), 'app.db.session.query', 'db.session.query', ({(94, 29, 94, 48): 'products_categories'}, {}), '(products_categories)', False, 'from app import db\n'), ((21, 34, 21, 45), 'app.db.exists', 'db.exists', ({}, {}), '()', False, 'from app import db\n')]