from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
import json
import sqlite3
import sys
import urllib.parse

from pms.errors import PMSError
from pms.service import PMSService, ensure_pms_tables


ROOT = Path(__file__).resolve().parent
DB_PATH = ROOT / "hotel.db"

DEFAULT_STAFF_ACCOUNTS = [
    {"username": "frontdesk", "password": "1111", "department": "frontdesk", "role": "reception"},
    {"username": "food", "password": "1111", "department": "food", "role": "staff"},
    {"username": "pool", "password": "1111", "department": "pool", "role": "staff"},
    {"username": "housekeeping", "password": "1111", "department": "housekeeping", "role": "staff"},
    {"username": "maintenance", "password": "1111", "department": "maintenance", "role": "staff"},
    {"username": "concierge", "password": "1111", "department": "concierge", "role": "staff"},
    {"username": "laundry", "password": "1111", "department": "laundry", "role": "staff"},
    {"username": "wellness", "password": "1111", "department": "wellness", "role": "staff"},
    {"username": "gym", "password": "1111", "department": "gym", "role": "staff"},
    {"username": "security", "password": "1111", "department": "security", "role": "staff"},
    {"username": "transport", "password": "1111", "department": "transport", "role": "staff"},
    {"username": "events", "password": "1111", "department": "events", "role": "manager"},
    {"username": "vip", "password": "1111", "department": "vip", "role": "manager"},
]


def connect():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn


def init_db():
    with connect() as conn:
        conn.execute(
            "create table if not exists app_state (key text primary key, value text not null)"
        )
        conn.execute(
            "create table if not exists orders (id text primary key, data text not null)"
        )
        conn.execute(
            "create table if not exists staff_accounts (username text primary key, password text not null, department text not null, role text not null default 'staff')"
        )
        columns = [row[1] for row in conn.execute("pragma table_info(staff_accounts)")]
        if "role" not in columns:
            conn.execute("alter table staff_accounts add column role text not null default 'staff'")
        conn.execute("update staff_accounts set role = 'reception' where username = 'frontdesk' and role = 'staff'")
        count = conn.execute("select count(*) from staff_accounts").fetchone()[0]
        if count == 0:
            conn.executemany(
                "insert into staff_accounts (username, password, department, role) values (:username, :password, :department, :role)",
                DEFAULT_STAFF_ACCOUNTS,
            )
        ensure_pms_tables(conn)


def get_state(conn, key, default=None):
    row = conn.execute("select value from app_state where key = ?", (key,)).fetchone()
    return json.loads(row["value"]) if row else default


def set_state(conn, key, value):
    conn.execute(
        "insert into app_state (key, value) values (?, ?) on conflict(key) do update set value = excluded.value",
        (key, json.dumps(value, ensure_ascii=False)),
    )


def hotel_slug(query):
    value = (query.get("hotel") or [""])[0].strip().lower()
    return value or "default"


def scoped_key(key, hotel):
    if key == "superHotels":
        return key
    return key if hotel == "default" else f"hotel:{hotel}:{key}"


def get_hotel_state(conn, hotel, key, default=None):
    return get_state(conn, scoped_key(key, hotel), default)


def set_hotel_state(conn, hotel, key, value):
    set_state(conn, scoped_key(key, hotel), value)

def read_orders(conn):
    return [
        json.loads(row["data"])
        for row in conn.execute("select data from orders order by rowid desc")
    ]


def department_service_ids(conn, department, hotel="default"):
    config = get_hotel_state(conn, hotel, "config", {}) or {}
    return {
        service.get("id")
        for service in config.get("services", [])
        if service.get("department") == department and service.get("id")
    }


def filter_orders(conn, query):
    orders = read_orders(conn)
    hotel = hotel_slug(query)
    room = (query.get("room") or [""])[0]
    department = (query.get("department") or [""])[0]
    status = (query.get("status") or [""])[0]
    orders = [order for order in orders if str(order.get("hotel") or "default") == hotel]
    if room:
        orders = [order for order in orders if str(order.get("room")) == str(room)]
    if department:
        service_ids = department_service_ids(conn, department, hotel)
        orders = [order for order in orders if order.get("serviceId") in service_ids]
    if status and status != "all":
        orders = [order for order in orders if order.get("status") == status]
    return orders
class HotelHandler(SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
        self.send_header("Pragma", "no-cache")
        self.send_header("Expires", "0")
        super().end_headers()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(ROOT), **kwargs)

    def send_json(self, status, payload):
        body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def read_json(self):
        length = int(self.headers.get("Content-Length") or 0)
        if not length:
            return None
        return json.loads(self.rfile.read(length).decode("utf-8"))

    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        path = parsed.path
        query = urllib.parse.parse_qs(parsed.query)
        hotel = hotel_slug(query)
        if path in ("/subscriptions", "/owner", "/superadmin"):
            self.path = "/subscriptions.html"
            super().do_GET()
            return
        if path.startswith("/hotel/") and path.rstrip("/").endswith("/report"):
            self.path = "/report.html"
            super().do_GET()
            return
        if path.startswith("/hotel/"):
            self.path = "/index.html"
            super().do_GET()
            return
        if path == "/api/super-hotels":
            with connect() as conn:
                self.send_json(200, {"superHotels": get_state(conn, "superHotels", [])})
            return
        if path == "/api/orders":
            with connect() as conn:
                self.send_json(200, {"orders": filter_orders(conn, query)})
            return
        if path == "/api/pms/connection-status":
            with connect() as conn:
                rooms = get_hotel_state(conn, hotel, "rooms", []) or []
                service = PMSService(conn, hotel, rooms)
                self.send_json(200, service.public_connection_status())
            return
        if path.startswith("/api/pms/reservation/"):
            room_id = urllib.parse.unquote(path.rsplit("/", 1)[-1])
            with connect() as conn:
                rooms = get_hotel_state(conn, hotel, "rooms", []) or []
                service = PMSService(conn, hotel, rooms)
                try:
                    self.send_json(200, service.get_reservation(room_id))
                except PMSError as error:
                    self.send_json(400, {"error": error.code, "message": error.message})
            return
        if path == "/api/bootstrap":
            with connect() as conn:
                staff_accounts = [
                    dict(row)
                    for row in conn.execute(
                        "select username, password, department, role from staff_accounts order by username"
                    )
                ]
                self.send_json(
                    200,
                    {
                        "staffAccounts": get_hotel_state(conn, hotel, "staffAccounts", staff_accounts if hotel == "default" else DEFAULT_STAFF_ACCOUNTS),
                        "config": get_hotel_state(conn, hotel, "config"),
                        "rooms": get_hotel_state(conn, hotel, "rooms"),
                        "departments": get_hotel_state(conn, hotel, "departments"),
                        "currentRoom": get_hotel_state(conn, hotel, "currentRoom"),
                        "guestAccounts": get_hotel_state(conn, hotel, "guestAccounts", {}),
                        "hotelLogo": get_hotel_state(conn, hotel, "hotelLogo", ""),
                        "hotelName": get_hotel_state(conn, hotel, "hotelName", {"ar": "فندق لافينا", "en": "Lavina Hotel"} if hotel == "default" else None),
                        "hotelTheme": get_hotel_state(conn, hotel, "hotelTheme", {}),
                        "heroImages": get_hotel_state(conn, hotel, "heroImages", []),
                        "heroSlideSeconds": get_hotel_state(conn, hotel, "heroSlideSeconds", 5),
                        "hotelOffers": get_hotel_state(conn, hotel, "hotelOffers", []),
                        "pmsConnection": PMSService(conn, hotel, get_hotel_state(conn, hotel, "rooms", []) or []).get_connection(),
                        "superHotels": get_state(conn, "superHotels", []),
                    },
                )
            return
        super().do_GET()
    def do_DELETE(self):
        parsed = urllib.parse.urlparse(self.path)
        path = parsed.path
        query = urllib.parse.parse_qs(parsed.query)
        hotel = hotel_slug(query)
        if path in ("/subscriptions", "/owner", "/superadmin"):
            self.path = "/subscriptions.html"
            super().do_GET()
            return
        if path.startswith("/hotel/") and path.rstrip("/").endswith("/report"):
            self.path = "/report.html"
            super().do_GET()
            return
        if path.startswith("/hotel/"):
            self.path = "/index.html"
            super().do_GET()
            return
        if path == "/api/super-hotels":
            with connect() as conn:
                self.send_json(200, {"superHotels": get_state(conn, "superHotels", [])})
            return
        if path == "/api/orders":
            room = (query.get("room") or [""])[0]
            order_id = (query.get("id") or [""])[0]
            with connect() as conn:
                if room:
                    rows = conn.execute("select id, data from orders").fetchall()
                    deleted = 0
                    for row in rows:
                        try:
                            order = json.loads(row["data"])
                        except Exception:
                            continue
                        if str(order.get("hotel") or "default") == hotel and str(order.get("room")) == str(room):
                            conn.execute("delete from orders where id = ?", (row["id"],))
                            deleted += 1
                    self.send_json(200, {"ok": True, "deleted": deleted})
                    return
                if order_id:
                    rows = conn.execute("select id, data from orders where id = ?", (order_id,)).fetchall()
                    deleted = 0
                    for row in rows:
                        order = json.loads(row["data"])
                        if str(order.get("hotel") or "default") == hotel:
                            conn.execute("delete from orders where id = ?", (row["id"],))
                            deleted += 1
                    self.send_json(200, {"ok": True, "deleted": deleted})
                    return
            self.send_json(400, {"error": "Missing room or id"})
            return
        self.send_json(404, {"error": "Not found"})
    def do_PUT(self):
        self.handle_write()

    def do_POST(self):
        self.handle_write()

    def handle_write(self):
        parsed = urllib.parse.urlparse(self.path)
        path = parsed.path
        query = urllib.parse.parse_qs(parsed.query)
        hotel = hotel_slug(query)
        payload = self.read_json()
        if payload is None:
            self.send_json(400, {"error": "Missing JSON body"})
            return

        with connect() as conn:
            if path == "/api/pms/config":
                rooms = get_hotel_state(conn, hotel, "rooms", []) or []
                service = PMSService(conn, hotel, rooms)
                config = service.save_connection(payload if isinstance(payload, dict) else {})
                self.send_json(200, {"ok": True, "connection": config})
                return

            if path == "/api/pms/verify-room":
                rooms = get_hotel_state(conn, hotel, "rooms", []) or []
                service = PMSService(conn, hotel, rooms)
                try:
                    self.send_json(200, service.verify_room(payload if isinstance(payload, dict) else {}))
                except PMSError as error:
                    self.send_json(400, {"error": error.code, "message": error.message})
                return

            if path == "/api/pms/post-charge":
                rooms = get_hotel_state(conn, hotel, "rooms", []) or []
                service = PMSService(conn, hotel, rooms)
                try:
                    self.send_json(200, service.post_charge(payload if isinstance(payload, dict) else {}))
                except PMSError as error:
                    self.send_json(400, {"error": error.code, "message": error.message})
                return

            if path == "/api/pms/webhook":
                rooms = get_hotel_state(conn, hotel, "rooms", []) or []
                service = PMSService(conn, hotel, rooms)
                self.send_json(202, service.store_webhook(payload if isinstance(payload, dict) else {}))
                return
            if path == "/api/super-hotels":
                hotels = payload.get("superHotels") if isinstance(payload, dict) else payload
                if not isinstance(hotels, list):
                    self.send_json(400, {"error": "superHotels payload must be a list"})
                    return
                set_state(conn, "superHotels", hotels)
                self.send_json(200, {"ok": True})
                return

            if path == "/api/orders":
                if not isinstance(payload, list):
                    self.send_json(400, {"error": "Orders payload must be a list"})
                    return
                for order in payload:
                    order["hotel"] = hotel
                    order_id = order.get("id")
                    if not order_id:
                        continue
                    existing_row = conn.execute("select data from orders where id = ?", (order_id,)).fetchone()
                    should_write = True
                    if existing_row:
                        try:
                            existing = json.loads(existing_row["data"])
                            incoming_time = order.get("updatedAt") or order.get("createdAt") or ""
                            existing_time = existing.get("updatedAt") or existing.get("createdAt") or ""
                            should_write = incoming_time >= existing_time
                        except Exception:
                            should_write = True
                    if should_write:
                        conn.execute(
                            "insert or replace into orders (id, data) values (?, ?)",
                            (order_id, json.dumps(order, ensure_ascii=False)),
                        )
                self.send_json(200, {"ok": True})
                return

            if path == "/api/staff-accounts":
                if not isinstance(payload, list):
                    self.send_json(400, {"error": "Staff accounts payload must be a list"})
                    return
                clean_accounts = [
                    account
                    for account in [{**account, "role": account.get("role") or "staff"} for account in payload]
                    if account.get("username") and account.get("password") and account.get("department")
                ]
                set_hotel_state(conn, hotel, "staffAccounts", clean_accounts)
                if hotel == "default":
                    conn.execute("delete from staff_accounts")
                    conn.executemany(
                        "insert into staff_accounts (username, password, department, role) values (:username, :password, :department, :role)",
                        clean_accounts,
                    )
                self.send_json(200, {"ok": True})
                return

            if path == "/api/config":
                set_hotel_state(conn, hotel, "config", payload.get("config"))
                set_hotel_state(conn, hotel, "rooms", payload.get("rooms"))
                set_hotel_state(conn, hotel, "departments", payload.get("departments"))
                set_hotel_state(conn, hotel, "currentRoom", payload.get("currentRoom"))
                set_hotel_state(conn, hotel, "guestAccounts", payload.get("guestAccounts") or {})
                set_hotel_state(conn, hotel, "hotelLogo", payload.get("hotelLogo") or "")
                set_hotel_state(conn, hotel, "hotelName", payload.get("hotelName") or {"ar": "فندق لافينا", "en": "Lavina Hotel"})
                set_hotel_state(conn, hotel, "hotelTheme", payload.get("hotelTheme") or {})
                set_hotel_state(conn, hotel, "heroImages", payload.get("heroImages") or [])
                set_hotel_state(conn, hotel, "heroSlideSeconds", payload.get("heroSlideSeconds") or 5)
                set_hotel_state(conn, hotel, "hotelOffers", payload.get("hotelOffers") or [])
                set_state(conn, "superHotels", payload.get("superHotels") or [])
                accounts = payload.get("staffAccounts")
                if isinstance(accounts, list):
                    clean_accounts = [
                        account
                        for account in [{**account, "role": account.get("role") or "staff"} for account in accounts]
                        if account.get("username") and account.get("password") and account.get("department")
                    ]
                    set_hotel_state(conn, hotel, "staffAccounts", clean_accounts)
                    if hotel == "default":
                        conn.execute("delete from staff_accounts")
                        conn.executemany(
                            "insert into staff_accounts (username, password, department, role) values (:username, :password, :department, :role)",
                            clean_accounts,
                        )
                self.send_json(200, {"ok": True})
                return

        self.send_json(404, {"error": "Not found"})


def main():
    init_db()
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8087
    server = ThreadingHTTPServer(("127.0.0.1", port), HotelHandler)
    print(f"Hotel system running at http://127.0.0.1:{port}/index.html")
    server.serve_forever()


if __name__ == "__main__":
    main()




















