from flask import Blueprint, render_template, request, session, redirect, url_for, jsonify, flash, current_app, json
from bson import ObjectId
from werkzeug.security import generate_password_hash
from AHP_ranking import ahp_rank_products, ahp_rank_selected_products, determine_weights_by_cluster
import pandas as pd
import os
from werkzeug.utils import secure_filename
import uuid

user_bp = Blueprint('user', __name__, template_folder='templates')

def pagination_query_string(page):
    args = request.args.to_dict()
    args['page'] = page
    return '&'.join(f"{k}={v}" for k, v in args.items())

@user_bp.route('/search')
def search_results():
    import re
    from bson.regex import Regex
    from flask import request, render_template, current_app

    db = current_app.db
    kmeans_collection = db['kmeans']

    # Get inputs
    keyword = request.args.get("q", "").strip().lower()
    filter_type = request.args.get("filter", "all").lower()
    page = int(request.args.get("page", 1))
    per_page = 6

    query = {}
    keyword_clauses = []

    # --- Extract Price Filters ---
    price_match_between = re.search(r'between\s+(\d+)\s+and\s+(\d+)', keyword)
    price_match_under = re.search(r'under\s+(\d+)', keyword)
    price_match_above = re.search(r'above\s+(\d+)', keyword)

    if price_match_between:
        min_price = float(price_match_between.group(1))
        max_price = float(price_match_between.group(2))
        query['price_actual_original'] = {"$gte": min_price, "$lte": max_price}
        keyword = re.sub(r'between\s+\d+\s+and\s+\d+', '', keyword).strip()
    elif price_match_under:
        max_price = float(price_match_under.group(1))
        query['price_actual_original'] = {"$lte": max_price}
        keyword = re.sub(r'under\s+\d+', '', keyword).strip()
    elif price_match_above:
        min_price = float(price_match_above.group(1))
        query['price_actual_original'] = {"$gte": min_price}
        keyword = re.sub(r'above\s+\d+', '', keyword).strip()

    # --- Extract Cluster Type from Keywords ---
    # This is overridden if filter_type is explicitly set
    if "premium" in keyword:
        query["cluster_label"] = "Premium Pick"
        keyword = keyword.replace("premium", "").strip()
    elif "smartbuy" in keyword or "smart buy" in keyword:
        query["cluster_label"] = "Smart Buy"
        keyword = keyword.replace("smartbuy", "").replace("smart buy", "").strip()

    # --- Manual Cluster Filter Override ---
    if filter_type == "premium":
        query["cluster_label"] = "Premium Pick"
    elif filter_type == "smartbuy":
        query["cluster_label"] = "Smart Buy"

    # --- Token Matching Against Title and Category Fields ---
    tokens = [t for t in keyword.split() if t]
    if tokens:
        regex_clauses = []
        for token in tokens:
            regex = Regex(token, "i")
            regex_clauses.append({
                "$or": [
                    {"title": regex},
                    {"main_category_original": regex},
                    {"sub_category_1_original": regex},
                    {"sub_category_2_original": regex}
                ]
            })
        if regex_clauses:
            keyword_clauses.append({"$and": regex_clauses})

    # --- Combine Keyword Clauses with Query ---
    if keyword_clauses:
        if "$and" in query:
            query["$and"].extend(keyword_clauses)
        else:
            query["$and"] = keyword_clauses

    # --- Execute Query ---
    total_results = kmeans_collection.count_documents(query)
    total_pages = max((total_results + per_page - 1) // per_page, 1)

    cursor = kmeans_collection.find(query).skip((page - 1) * per_page).limit(per_page)
    results = list(cursor)

    for product in results:
        product['_id'] = str(product['_id'])

    return render_template(
        "User/search_result.html",
        results=results,
        page=page,
        total_pages=total_pages,
        total_products=total_results,
        start_index=(page - 1) * per_page + 1,
        end_index=min(page * per_page, total_results),

    )


@user_bp.route('/filter/<filter_type>')
def filtered_products(filter_type):
    db = current_app.db
    kmeans_collection = db['kmeans']

    filter_type = filter_type.strip().lower()
    if filter_type == 'all':
        products = list(kmeans_collection.find())
    elif filter_type in ['premium', 'premium pick']:
        products = list(kmeans_collection.find({'tag': 'Premium Pick'}))
    elif filter_type in ['smartbuy', 'smart buy']:
        products = list(kmeans_collection.find({'tag': 'Smart Buy'}))
    else:
        products = []

    for product in products:
        product['_id'] = str(product['_id'])

    return render_template('User/search_result.html', products=products, filter_type=filter_type)


@user_bp.route('/compare')
def compare_selected():
    db = current_app.db
    ids = request.args.getlist('product_ids')
    if len(ids) < 2 or len(ids) > 3:
        return "Please select 2 or 3 products to compare.", 400

    clusters = [db['kmeans'].find_one({'_id': ObjectId(i)}).get('cluster_label', '') for i in ids]
    main_cluster = max(set(clusters), key=clusters.count) if clusters else ""
    weights = determine_weights_by_cluster(main_cluster)

    df = pd.DataFrame(list(db['kmeans'].find()))
    df['_id'] = df['_id'].astype(str)

    try:
        ranked_df = ahp_rank_selected_products(df, selected_ids=ids, criteria_weights=weights)
    except Exception as e:
        return f"AHP ranking failed: {e}", 500

    products = ranked_df.to_dict(orient='records')
    for p in products:
        p['score'] = round(p['AHP_score'] * 100, 2)

    return render_template('User/compare_multi.html', products=products)

@user_bp.route('/compare_pair')
def compare_pair():
    db = current_app.db
    ids = request.args.getlist('product_ids')
    if len(ids) != 2:
        return "Please select exactly 2 products.", 400

    clusters = [db['kmeans'].find_one({'_id': ObjectId(i)}).get('cluster_label', '') for i in ids]
    main_cluster = max(set(clusters), key=clusters.count) if clusters else ""
    weights = determine_weights_by_cluster(main_cluster)

    df = pd.DataFrame(list(db['kmeans'].find()))
    df['_id'] = df['_id'].astype(str)

    try:
        pair_ranked = ahp_rank_selected_products(df, selected_ids=ids, criteria_weights=weights)
    except Exception as e:
        return f"AHP ranking failed: {e}", 500

    product1 = pair_ranked.iloc[0].to_dict()
    product2 = pair_ranked.iloc[1].to_dict()
    recommended = product1

    try:
        similar_df = ahp_rank_products(df=df, selected_product_id=recommended['_id'], price_margin=0.15, criteria_weights=weights, max_items=6)
        alternatives = similar_df[similar_df['_id'] != recommended['_id']].to_dict(orient='records')
    except Exception as e:
        alternatives = []

    return render_template('User/compare_pair.html', product1=product1, product2=product2, recommended=recommended, alternatives=alternatives)

@user_bp.route('/view/<product_id>')
def view_product(product_id):
    db = current_app.db
    try:
        product = db['kmeans'].find_one({'_id': ObjectId(product_id)})
    except Exception:
        return "Invalid product ID", 400

    if not product:
        return "Product not found", 404

    all_products = list(db['kmeans'].find())
    for p in all_products:
        p['_id'] = str(p['_id'])

    df = pd.DataFrame(all_products)

    required_fields = ['_id', 'title', 'specification', 'price_actual_original', 'cluster_label', 'item_category_detail', 'price_actual_display', 'item_rating_display', 'total_sold_display', 'seller_name']
    if not all(field in df.columns for field in required_fields):
        return "Dataset missing required fields for AHP ranking.", 500

    selected_product_id = str(product['_id'])
    weights = determine_weights_by_cluster(product.get('cluster_label', ''))

    cart_ids = []
    if 'user_id' in session:
        cart_entries = db['cart'].find({'user_id': session['user_id']})
        cart_ids = [str(entry['product_id']) for entry in cart_entries]

    sellers = []
    similar_ranked_products = []

    try:
        ranked_df = ahp_rank_products(df, selected_product_id, criteria_weights=weights)
        ranked_df['rank'] = ranked_df['AHP_score'].rank(ascending=False, method='min').astype(int)
        sellers = ranked_df.to_dict(orient='records')

        for s in sellers:
            s['name'] = s.get('seller_name', 'Unknown')
            s['price_actual_display'] = s.get('price_actual_display') or 'N/A'
            s['total_sold'] = int(s.get('total_sold_display') or 0)
            s['rating'] = round(float(s.get('item_rating_display') or 0), 2)
            s['score'] = round(float(s.get('AHP_score') or 0), 3)

        similar_ranked_products = [
            {
                '_id': s['_id'],
                'title': s['title'],
                'seller_name': s.get('seller_name', 'Unknown'),
                'price_actual_display': s.get('price_actual_display', 'N/A'),
                'score': round(s.get('AHP_score', 0), 3),
                'rank': s.get('rank'),
                'pict_link': s.get('pict_link', '')
            }
            for s in sellers if s['_id'] != selected_product_id
        ][:6]

    except Exception as e:
        print("❌ AHP ranking error:", str(e))


    return render_template('User/product_overview.html', product=product, sellers=sellers, similar_ranked_products=similar_ranked_products,cart=cart_ids)


@user_bp.route('/purchase/<product_id>', methods=['GET', 'POST'])
def purchase(product_id):
    if 'user_id' not in session:
        flash("Please log in to continue your purchase.", "warning")
        return redirect(url_for('auth.login', next=request.url))  # redirect back after login

    db = current_app.db
    product = db['kmeans'].find_one({'_id': ObjectId(product_id)})

    if not product:
        return "Product not found", 404

    if request.method == 'POST':
        fullname = request.form.get('fullname')
        email = request.form.get('email')
        address = request.form.get('address')
        phone = request.form.get('phone')
        user_id = session.get('user_id')

        form_data = {
            'fullname': fullname,
            'email': email,
            'address': address,
            'phone': phone
        }

        return render_template('User/payment.html', product=product, form_data=form_data)

    return render_template('User/purchase.html', product=product)



@user_bp.route('/finalize_purchase/<product_id>', methods=['POST'])
def finalize_purchase(product_id):
    from bson.objectid import ObjectId
    import uuid
    import requests

    db = current_app.db
    product = db['kmeans'].find_one({'_id': ObjectId(product_id)})
    if not product:
        return "Product not found", 404

    form = request.form
    buyer_name = form.get('fullname')
    email = form.get('email')
    phone = form.get('phone')
    address = form.get('address')

    # -- Create ToyyibPay bill
    payload = {
        'userSecretKey': 'YOUR_TOYYIBPAY_SECRET_KEY',
        'categoryCode': 'YOUR_CATEGORY_CODE',
        'billName': f"Purchase: {product['title']}",
        'billDescription': f"{product['title']} from Ecobiz",
        'billPriceSetting': 1,
        'billPayorInfo': 1,
        'billAmount': int(float(product['price_actual_original']) * 100),  # in cents
        'billReturnUrl': 'https://yourdomain.com/thank-you',
        'billCallbackUrl': 'https://yourdomain.com/payment-callback',
        'billExternalReferenceNo': str(uuid.uuid4()),
        'billTo': buyer_name,
        'billEmail': email,
        'billPhone': phone,
        'billPaymentChannel': '0'
    }

    response = requests.post('https://toyyibpay.com/index.php/api/createBill', data=payload)
    data = response.json()

    if not data or 'BillCode' not in data[0]:
        return "Failed to create bill", 500

    bill_code = data[0]['BillCode']

    # -- Save to sales collection
    db['sales'].insert_one({
        'product_id': str(product['_id']),
        'product_title': product.get('title'),
        'price': float(product.get('price_actual_original', 0)),
        'fullname': buyer_name,
        'email': email,
        'phone': phone,
        'address': address,
        'payment_method': 'ToyyibPay',
        'status': 'pending',
        'bill_code': bill_code,
        'timestamp': pd.Timestamp.now()
    })

    return redirect(f'https://toyyibpay.com/{bill_code}')

@user_bp.route("/redirecting/<product_id>")
def redirect_to_purchase(product_id):
    return render_template("User/redirecting.html", product_id=product_id)

@user_bp.route('/payment/<product_id>', methods=['GET', 'POST'])
def payment(product_id):
    db = current_app.db
    product = db['kmeans'].find_one({'_id': ObjectId(product_id)})
    if not product:
        return "Product not found", 404

    if request.method == 'POST':
        form_data = request.form.to_dict()
        return render_template("User/payment.html",
                               product=product,
                               form_data=form_data,
)

    return redirect(url_for('user.purchase', product_id=product_id))

@user_bp.route('/profile')
def profile():
    if 'user_id' not in session:
        flash("Please log in to access your profile.", "warning")
        return redirect(url_for('auth.login'))

    db = current_app.db
    user = db['Users'].find_one({'_id': ObjectId(session['user_id'])})

    if user is None:
        session.pop('user_id', None)
        flash("Session expired. Please log in again.", "warning")
        return redirect(url_for('auth.login'))

    # 🔄 Auto-fill phone from sales if it's missing
    if not user.get('phone'):
        recent_sale = db['sales'].find_one({'email': user.get('email')})
        if recent_sale and recent_sale.get('phone'):
            user['phone'] = recent_sale['phone']
            db['Users'].update_one({'_id': user['_id']}, {'$set': {'phone': user['phone']}})

    # 🧾 Fetch orders
    orders = list(db['sales'].find({'email': user.get('email')}))

    # Fetch cart items
    cart_entries = list(db['cart'].find({'user_id': session['user_id']}))
    cart_ids = [entry['product_id'] for entry in cart_entries]
    cart_products = list(db['kmeans'].find({'_id': {'$in': [ObjectId(pid) for pid in cart_ids]}}))
    for product in cart_products:
        product['_id'] = str(product['_id'])

    return render_template("User/profile.html", user=user, orders=orders, cart=cart_products)


import os
import uuid
from flask import current_app, request, redirect, url_for, flash, session, render_template
from werkzeug.utils import secure_filename
from bson import ObjectId

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@user_bp.route('/profile/edit', methods=['GET', 'POST'])
def edit_profile():
    if 'user_id' not in session:
        flash("Login required.", "warning")
        return redirect(url_for('auth.login'))

    db = current_app.db
    user = db['Users'].find_one({'_id': ObjectId(session['user_id'])})

    if not user:
        flash("User not found.", "danger")
        return redirect(url_for('auth.login'))

    if request.method == 'POST':
        firstname = request.form.get('firstname')
        lastname = request.form.get('lastname')
        phone = request.form.get('phone')

        update_fields = {
            'first_name': firstname,
            'last_name': lastname,
            'phone': phone
        }

        avatar = request.files.get('avatar')
        if avatar and allowed_file(avatar.filename):
            filename = secure_filename(avatar.filename)
            unique_filename = f"{uuid.uuid4().hex}_{filename}"

            upload_folder = os.path.join(current_app.root_path, 'static', 'uploads')
            os.makedirs(upload_folder, exist_ok=True)
            avatar.save(os.path.join(upload_folder, unique_filename))

            update_fields['avatar'] = unique_filename

        db['Users'].update_one({'_id': user['_id']}, {'$set': update_fields})
        flash("Profile updated successfully!", "success")
        return redirect(url_for('user.profile'))

    return render_template('User/edit_profile.html', user=user)


@user_bp.route('/cart/toggle/<product_id>', methods=['POST'])
def toggle_cart(product_id):
    db = current_app.db
    if 'user_id' not in session:
        flash("Login required to manage cart.", "warning")
        return redirect(url_for('auth.login'))

    user_id = session['user_id']
    existing = db['cart'].find_one({'user_id': user_id, 'product_id': product_id})

    if existing:
        db['cart'].delete_one({'_id': existing['_id']})
        flash('Removed from cart', 'info')
        referrer = request.referrer or url_for('user.profile')
        if '/profile' in referrer:
            return redirect(url_for('user.profile') + '#cart')
        return redirect(referrer)
    else:
        db['cart'].insert_one({'user_id': user_id, 'product_id': product_id})
        flash('Added to cart', 'success')
        return redirect(url_for('user.view_product', product_id=product_id, added='1'))


# ✅ Place this OUTSIDE toggle_cart
@user_bp.route('/purchase_multiple', methods=['POST'])
def purchase_multiple():
    if 'user_id' not in session:
        flash("Please log in to continue your purchase.", "warning")
        return redirect(url_for('auth.login'))

    db = current_app.db
    ids_json = request.form.get('selected_ids')
    if not ids_json:
        flash("No products selected for checkout.", "warning")
        return redirect(url_for('user.profile'))

    try:
        selected_ids = json.loads(ids_json)
    except Exception:
        flash("Invalid product selection format.", "danger")
        return redirect(url_for('user.profile'))

    products = list(db['kmeans'].find({'_id': {'$in': [ObjectId(i) for i in selected_ids]}}))
    for p in products:
        p['_id'] = str(p['_id'])
        try:
            # Ensure price is a float
            p['price_actual_original'] = float(p.get('price_actual_original', 0))
        except:
            p['price_actual_original'] = 0

    return render_template("User/purchase.html", products=products)

@user_bp.route('/remove_multiple', methods=['POST'])
def remove_multiple():
    if 'user_id' not in session:
        flash("Please log in to continue.", "warning")
        return redirect(url_for('auth.login'))

    db = current_app.db
    ids_json = request.form.get('selected_ids')
    if not ids_json:
        flash("No products selected to remove.", "warning")
        return redirect(url_for('user.profile'))

    try:
        selected_ids = json.loads(ids_json)
    except Exception:
        flash("Invalid selection data.", "danger")
        return redirect(url_for('user.profile'))

    db['cart'].delete_many({'user_id': session['user_id'], 'product_id': {'$in': selected_ids}})
    flash("Selected products removed from your cart.", "success")
    return redirect(url_for('user.profile'))

@user_bp.route('/payment_multiple', methods=['POST'])
def payment_multiple():
    if 'user_id' not in session:
        flash("Please log in to proceed.", "warning")
        return redirect(url_for('auth.login'))

    db = current_app.db

    # Get product IDs from the form
    product_ids = request.form.getlist('product_ids')
    products = list(db['kmeans'].find({'_id': {'$in': [ObjectId(pid) for pid in product_ids]}}))
    for p in products:
        p['_id'] = str(p['_id'])
        try:
            p['price_actual_original'] = float(p.get('price_actual_original', 0))
        except:
            p['price_actual_original'] = 0

    form_data = {
        'fullname': request.form.get('first_name', '') + ' ' + request.form.get('last_name', ''),
        'email': request.form.get('email'),
        'address': request.form.get('address'),
        'phone': request.form.get('phone'),
        'city': request.form.get('city'),
        'state': request.form.get('state'),
        'zip': request.form.get('zip'),
        'country': request.form.get('country')
    }

    return render_template('User/payment.html', products=products, form_data=form_data)

from utils.toyyibpay import create_bill
import uuid
import pandas as pd

@user_bp.route('/finalize_multiple', methods=['POST'])
def finalize_multiple():
    from bson.objectid import ObjectId
    import uuid
    import requests

    db = current_app.db

    product_ids = request.form.getlist('product_ids')
    first_name = request.form.get('first_name')
    last_name = request.form.get('last_name')
    email = request.form.get('email')
    phone = request.form.get('phone')
    address = request.form.get('address')
    fullname = f"{first_name} {last_name}"

    products = list(db['kmeans'].find({'_id': {'$in': [ObjectId(pid) for pid in product_ids]}}))
    if not products:
        return "No products found", 400

    total_price = sum(float(p.get('price_actual_original', 0)) for p in products)

    # -- Create ToyyibPay bill for all items
    payload = {
        'userSecretKey': 'YOUR_TOYYIBPAY_SECRET_KEY',
        'categoryCode': 'YOUR_CATEGORY_CODE',
        'billName': f"Ecobiz Order ({len(products)} items)",
        'billDescription': 'Payment for selected products',
        'billPriceSetting': 1,
        'billPayorInfo': 1,
        'billAmount': int(total_price * 100),
        'billReturnUrl': 'https://yourdomain.com/thank-you',
        'billCallbackUrl': 'https://yourdomain.com/payment-callback',
        'billExternalReferenceNo': str(uuid.uuid4()),
        'billTo': fullname,
        'billEmail': email,
        'billPhone': phone,
        'billPaymentChannel': '0'
    }

    response = requests.post('https://toyyibpay.com/index.php/api/createBill', data=payload)
    data = response.json()

    if not data or 'BillCode' not in data[0]:
        return "Failed to create ToyyibPay bill", 500

    bill_code = data[0]['BillCode']

    # -- Save each product as its own sales record
    for product in products:
        db['sales'].insert_one({
            'product_id': str(product['_id']),
            'product_title': product.get('title'),
            'price': float(product.get('price_actual_original', 0)),
            'fullname': fullname,
            'email': email,
            'phone': phone,
            'address': address,
            'payment_method': 'ToyyibPay',
            'status': 'pending',
            'bill_code': bill_code,
            'timestamp': pd.Timestamp.now()
        })

    # -- Optional: Clear cart if user is logged in
    if 'user_id' in session:
        db['cart'].delete_many({
            'user_id': session['user_id'],
            'product_id': {'$in': product_ids}
        })

    return redirect(f'https://toyyibpay.com/{bill_code}')



    @user_bp.route('/toyyibpay-callback', methods=["POST"])
    def toyyibpay_callback():
        db = current_app.db  # points to fyp
        data = request.form.to_dict(flat=False)

        bill_code = data.get("billCode", [None])[0]
        status_code = data.get("billpaymentStatus", ["2"])[0]
        is_paid = status_code == "1"

        clean_data = {k: v[0] for k, v in data.items()}

        # ✅ Update sales and store backup of callback
        db['sales'].update_many(
            {"bill_code": bill_code},
            {
                "$set": {
                    "status": "paid" if is_paid else "failed",
                    "callback_data": clean_data,  # ✅ full callback for backup
                    "paid_at": pd.Timestamp.now() if is_paid else None
                }
            }
        )

        return "Callback processed", 200


@user_bp.route('/toyyibpay-direct', methods=['POST'])
def toyyibpay_direct():
    from utils.toyyibpay import create_bill
    import uuid

    db = current_app.db

    # Get selected product(s)
    product_ids = request.form.getlist('product_ids')
    products = list(db['kmeans'].find({'_id': {'$in': [ObjectId(pid) for pid in product_ids]}}))

    if not products:
        return "No products found", 400

    # Total price
    total_amount = sum(float(p.get('price_actual_original', 0)) for p in products)

    fullname = request.form.get('first_name', '') + ' ' + request.form.get('last_name', '')
    email = request.form.get('email')
    phone = request.form.get('phone')
    address = request.form.get('address')

    order_id = str(uuid.uuid4())

    bill = create_bill(
        customer_name=fullname,
        email=email,
        phone=phone,
        amount=total_amount,
        order_id=order_id
    )

    if not bill or 'BillCode' not in bill:
        return "Error creating ToyyibPay bill", 500

    # Save the order in MongoDB
    db['sales'].insert_one({
        'product_ids': [str(p['_id']) for p in products],
        'products': [p['title'] for p in products],
        'price': total_amount,
        'fullname': fullname,
        'email': email,
        'phone': phone,
        'address': address,
        'payment_method': 'ToyyibPay',
        'status': 'pending',
        'bill_code': bill['BillCode'],
        'timestamp': pd.Timestamp.now()
    })

    return redirect(f"https://toyyibpay.com/{bill['BillCode']}")


@user_bp.route('/thank-you')
def thank_you():
    return render_template('User/thank_you.html')
