# -*- coding: utf-8 -*-
"""
Populates the POS database with realistic demo data for customer presentations.

Generates:
  - 1 manager account  (username: manager / PIN: 1234 / password: password)
  - 4 demo waiters
  - 2 table groups with 12 tables total
  - A full menu: 3 categories, ~20 products
  - 45 days of backdated history (business days, shifts, orders, payments)

Usage:
    python demo_seed.py

Must be run from the local_backend directory. Designed to run on a clean DB
(after wipe_database.py), but safe to run multiple times — checks for the
[DEMO] marker before inserting anything.
"""
import json
import random
import sys
from datetime import datetime, timedelta, timezone

import bcrypt
from sqlalchemy import text

from database import engine, Base, SessionLocal

import models.user
import models.table
import models.printer
import models.product
import models.order
import models.business_day
import models.shift
import models.settings
import models.flag
import models.message

from models.user import User, WaiterZone
from models.table import TableGroup, Table
from models.product import Category, Product
from models.order import Order, OrderItem, OrderWaiter, OrderAuditLog
from models.business_day import BusinessDay
from models.shift import WaiterShift

Base.metadata.create_all(bind=engine)

# ── Config ────────────────────────────────────────────────────────────────────

DEMO_DAYS = 120         # how many past days to generate (~4 months)
ORDERS_PER_DAY = (18, 35)   # min/max orders per business day

# ── Helpers ───────────────────────────────────────────────────────────────────

def _utc(dt: datetime) -> datetime:
    if dt.tzinfo is None:
        return dt.replace(tzinfo=timezone.utc)
    return dt


def _hash_pin(pin: str) -> str:
    return bcrypt.hashpw(pin.encode(), bcrypt.gensalt()).decode()


def _hash_password(pw: str) -> str:
    return bcrypt.hashpw(pw.encode(), bcrypt.gensalt()).decode()


def _audit(db, order_id, event_type, waiter_id=None, item_ids=None,
           amount=None, payment_method=None, note=None, created_at=None):
    log = OrderAuditLog(
        order_id=order_id,
        event_type=event_type,
        waiter_id=waiter_id,
        item_ids=json.dumps(item_ids) if item_ids is not None else None,
        amount=amount,
        payment_method=payment_method,
        note=note,
    )
    if created_at:
        log.created_at = _utc(created_at)
    db.add(log)


# ── Menu definition ───────────────────────────────────────────────────────────
# (category_name, color, [(product_name, price), ...])
MENU = [
    ("Ορεκτικά", "#f97316", [
        ("Τζατζίκι", 4.50),
        ("Ταραμοσαλάτα", 4.50),
        ("Χωριάτικη Σαλάτα", 7.50),
        ("Κεφτεδάκια", 8.00),
        ("Σαγανάκι", 6.50),
        ("Χούμους", 5.00),
    ]),
    ("Κυρίως Πιάτα", "#ef4444", [
        ("Μπριζόλα Χοιρινή", 12.00),
        ("Μπριζόλα Μοσχαρίσια", 16.00),
        ("Σουβλάκι Κοτόπουλο", 10.00),
        ("Μουσακάς", 11.00),
        ("Παστίτσιο", 10.00),
        ("Σπαγγέτι Μπολονέζ", 9.50),
        ("Γύρος Χοιρινός", 8.50),
    ]),
    ("Ποτά & Αναψυκτικά", "#3b82f6", [
        ("Νερό 500ml", 1.00),
        ("Αναψυκτικό", 2.50),
        ("Μπύρα Φιάλη", 4.00),
        ("Κρασί (ποτήρι)", 4.50),
        ("Καφές Ελληνικός", 2.50),
        ("Φραπέ", 3.00),
        ("Χυμός Πορτοκάλι", 3.50),
    ]),
]

# Weighted product picker: drinks ordered ~40% of the time, mains ~35%, starters ~25%
CATEGORY_WEIGHTS = [0.25, 0.35, 0.40]

WAITER_NAMES = [
    ("Νίκος",   "Νίκος Π."),
    ("Μαρία",   "Μαρία Κ."),
    ("Πέτρος",  "Πέτρος Α."),
    ("Έλενα",   "Έλενα Μ."),
]

PAYMENT_METHODS = ["cash", "cash", "cash", "card", "card"]   # 60% cash / 40% card

# ── Main ──────────────────────────────────────────────────────────────────────

db = SessionLocal()

try:
    # ── Guard: abort if demo data already present ─────────────────────────────
    existing = db.query(User).filter(User.full_name.like("[DEMO]%")).first()
    if existing:
        print("Demo data already present (found [DEMO] user). Nothing to do.")
        print("Run  python wipe_database.py  first if you want a fresh seed.")
        sys.exit(0)

    print("Creating demo data...")

    # ── Manager account ───────────────────────────────────────────────────────
    manager = db.query(User).filter(User.username == "manager").first()
    if not manager:
        manager = User(
            username="manager",
            pin_hash=_hash_pin("1234"),
            password_hash=_hash_password("password"),
            role="manager",
            full_name="Manager",
            is_active=True,
        )
        db.add(manager)
        db.flush()
        print("  created manager  (username: manager / PIN: 1234 / password: password)")
    else:
        # Ensure password_hash is set on existing manager
        if not manager.password_hash:
            manager.password_hash = _hash_password("password")
            db.flush()
        print("  manager account already exists — updated password to 'password'")

    # ── Waiters ───────────────────────────────────────────────────────────────
    waiters = []
    for nickname, full_name in WAITER_NAMES:
        username = f"demo_{nickname.lower()}"
        w = db.query(User).filter(User.username == username).first()
        if not w:
            w = User(
                username=username,
                pin_hash=_hash_pin(str(random.randint(1000, 9999))),
                role="waiter",
                full_name=f"[DEMO] {full_name}",
                nickname=nickname,
                is_active=True,
            )
            db.add(w)
            db.flush()
        waiters.append(w)
    print(f"  created {len(waiters)} demo waiters")

    # ── Table groups & tables ─────────────────────────────────────────────────
    group_configs = [
        ("Κεντρική Αίθουσα", "Κ", "#6366f1", list(range(1, 9))),    # tables 1-8
        ("Βεράντα",          "Β", "#22c55e", list(range(1, 6))),     # tables 1-5
    ]
    all_tables = []
    for g_name, g_prefix, g_color, table_numbers in group_configs:
        grp = db.query(TableGroup).filter(TableGroup.name == g_name).first()
        if not grp:
            grp = TableGroup(name=g_name, prefix=g_prefix, color=g_color, sort_order=len(all_tables))
            db.add(grp)
            db.flush()
        for n in table_numbers:
            tbl = db.query(Table).filter(Table.group_id == grp.id, Table.number == n).first()
            if not tbl:
                tbl = Table(number=n, group_id=grp.id, is_active=True)
                db.add(tbl)
                db.flush()
            all_tables.append(tbl)
    print(f"  created {len(all_tables)} tables across {len(group_configs)} groups")

    # ── Waiter zone assignments (all waiters → all zones) ─────────────────────
    for w in waiters:
        existing_zone = db.query(WaiterZone).filter(
            WaiterZone.waiter_id == w.id, WaiterZone.group_id.is_(None)
        ).first()
        if not existing_zone:
            db.add(WaiterZone(waiter_id=w.id, group_id=None))
    db.flush()

    # ── Menu ──────────────────────────────────────────────────────────────────
    categories = []
    all_products = []      # flat list for random picks
    category_product_map = []   # parallel list of product sub-lists (for weighted pick)

    for sort_idx, (cat_name, cat_color, items) in enumerate(MENU):
        cat = db.query(Category).filter(Category.name == cat_name).first()
        if not cat:
            cat = Category(name=cat_name, color=cat_color, sort_order=sort_idx)
            db.add(cat)
            db.flush()
        categories.append(cat)

        cat_products = []
        for prod_sort, (prod_name, prod_price) in enumerate(items):
            p = db.query(Product).filter(Product.name == prod_name, Product.category_id == cat.id).first()
            if not p:
                p = Product(
                    name=prod_name,
                    category_id=cat.id,
                    base_price=prod_price,
                    is_available=True,
                    lifecycle_status="active",
                    sort_order=prod_sort,
                )
                db.add(p)
                db.flush()
            cat_products.append(p)
            all_products.append(p)
        category_product_map.append(cat_products)

    print(f"  created {len(categories)} categories with {len(all_products)} products")

    db.commit()

    # ── Historical days ───────────────────────────────────────────────────────
    today = datetime.now(timezone.utc).date()
    total_orders = 0
    total_revenue = 0.0

    print(f"\nGenerating {DEMO_DAYS} days of history...")

    for days_ago in range(DEMO_DAYS, 0, -1):
        day_date = today - timedelta(days=days_ago)

        # Business day: opens at ~10:00, closes at ~23:30
        open_time  = _utc(datetime(day_date.year, day_date.month, day_date.day,
                                   10, random.randint(0, 30)))
        close_time = _utc(datetime(day_date.year, day_date.month, day_date.day,
                                   23, random.randint(15, 45)))

        bday = BusinessDay(
            status="closed",
            opened_at=open_time,
            opened_by_id=manager.id,
            closed_at=close_time,
            closed_by_id=manager.id,
        )
        db.add(bday)
        db.flush()

        # 2-4 waiters work each day
        working_waiters = random.sample(waiters, k=random.randint(2, len(waiters)))
        STARTING_CASH_OPTIONS = [50.0, 80.0, 100.0, 110.0, 120.0, 150.0, 200.0]
        shifts = {}
        shift_totals = {}   # shift_id → running revenue total, filled during order loop
        for w in working_waiters:
            shift_start = open_time + timedelta(minutes=random.randint(0, 30))
            shift_end   = close_time - timedelta(minutes=random.randint(0, 20))
            shift = WaiterShift(
                waiter_id=w.id,
                business_day_id=bday.id,
                started_at=shift_start,
                ended_at=shift_end,
                starting_cash=random.choice(STARTING_CASH_OPTIONS),
            )
            db.add(shift)
            db.flush()
            shifts[w.id] = shift
            shift_totals[shift.id] = 0.0

        # Orders — two rushes: lunch (12-15h) and dinner (19-23h)
        n_orders = random.randint(*ORDERS_PER_DAY)
        lunch_count  = int(n_orders * 0.4)
        dinner_count = n_orders - lunch_count

        order_times = []
        for _ in range(lunch_count):
            h = random.randint(12, 14)
            m = random.randint(0, 59)
            order_times.append(datetime(day_date.year, day_date.month, day_date.day, h, m))
        for _ in range(dinner_count):
            h = random.randint(19, 22)
            m = random.randint(0, 59)
            order_times.append(datetime(day_date.year, day_date.month, day_date.day, h, m))
        order_times.sort()

        # Track which tables are "busy" at each point in time so we don't double-seat
        # Format: {table_id: release_time}
        table_busy_until: dict[int, datetime] = {}

        for opened_at_naive in order_times:
            opened_at = _utc(opened_at_naive)

            # Pick a free table
            free_tables = [
                t for t in all_tables
                if table_busy_until.get(t.id, datetime.min.replace(tzinfo=timezone.utc)) <= opened_at
            ]
            if not free_tables:
                free_tables = all_tables   # all busy → just reuse (edge case)
            table = random.choice(free_tables)

            # Pick a waiter who is working today
            waiter = random.choice(working_waiters)
            shift  = shifts[waiter.id]

            # Order duration: 20-70 min
            duration_min = random.randint(20, 70)
            closed_at = opened_at + timedelta(minutes=duration_min)
            # Cap to close_time
            if closed_at > close_time:
                closed_at = close_time - timedelta(minutes=2)

            table_busy_until[table.id] = closed_at

            order = Order(
                table_id=table.id,
                opened_by=waiter.id,
                business_day_id=bday.id,
                status="closed",
                closed_by=manager.id,
                notes=None,
                source="pos",
            )
            order.opened_at = opened_at
            order.closed_at = closed_at
            db.add(order)
            db.flush()

            db.add(OrderWaiter(
                order_id=order.id,
                waiter_id=waiter.id,
                assigned_at=opened_at,
            ))

            _audit(db, order.id, "ORDER_OPENED", waiter_id=waiter.id, created_at=opened_at)

            # Items: 2-6 items, weighted by category
            n_items = random.randint(2, 6)
            chosen_category_lists = random.choices(category_product_map, weights=CATEGORY_WEIGHTS, k=n_items)
            items_added = []
            order_total = 0.0
            items_added_at = opened_at + timedelta(minutes=random.randint(2, 8))

            for prod_list in chosen_category_lists:
                product = random.choice(prod_list)
                qty = random.choices([1, 2, 3], weights=[0.7, 0.2, 0.1])[0]
                payment_method = random.choice(PAYMENT_METHODS)
                paid_at = closed_at - timedelta(minutes=random.randint(1, 5))
                line_total = product.base_price * qty
                order_total += line_total

                item = OrderItem(
                    order_id=order.id,
                    product_id=product.id,
                    added_by=waiter.id,
                    quantity=qty,
                    unit_price=product.base_price,
                    status="paid",
                    printed=True,
                    paid_by=waiter.id,
                    paid_at=_utc(paid_at),
                    payment_method=payment_method,
                    paid_in_shift_id=shift.id,
                )
                item.added_at = _utc(items_added_at)
                db.add(item)
                db.flush()
                items_added.append(item.id)
                shift_totals[shift.id] = round(shift_totals[shift.id] + line_total, 2)

            _audit(db, order.id, "ITEMS_ADDED",
                   waiter_id=waiter.id, item_ids=items_added,
                   created_at=items_added_at)

            # Payment audit (single payment per order for simplicity)
            payment_method = random.choice(PAYMENT_METHODS)
            pay_at = closed_at - timedelta(minutes=1)
            _audit(db, order.id, "PAYMENT",
                   waiter_id=waiter.id, item_ids=items_added,
                   amount=round(order_total, 2), payment_method=payment_method,
                   created_at=pay_at)

            _audit(db, order.id, "ORDER_CLOSED",
                   waiter_id=waiter.id, created_at=closed_at)

            total_orders  += 1
            total_revenue += order_total

        # Write total_collected snapshot onto each ended shift
        for w in working_waiters:
            s = shifts[w.id]
            s.total_collected = shift_totals[s.id]

        db.commit()
        print(f"  {day_date}  {n_orders:2d} orders  {len(working_waiters)} waiters")

    print(f"\nDone.")
    print(f"  Total orders   : {total_orders}")
    print(f"  Total revenue  : €{total_revenue:,.2f}")
    print(f"\nLogin credentials:")
    print(f"  Manager  — username: manager  |  password: password  |  PIN: 1234")
    for w in waiters:
        print(f"  Waiter   — username: {w.username}")

finally:
    db.close()
