from pathlib import Path
import json
import mimetypes
import sqlite3
import urllib.parse

from pms.errors import PMSError
from pms.service import PMSService
from server import (
    DEFAULT_STAFF_ACCOUNTS,
    DB_PATH,
    ROOT,
    department_service_ids,
    filter_orders,
    get_hotel_state,
    get_state,
    hotel_slug,
    init_db,
    set_hotel_state,
    set_state,
)


init_db()


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


def json_response(start_response, status, payload):
    body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
    start_response(
        f"{status} {status_text(status)}",
        [
            ("Content-Type", "application/json; charset=utf-8"),
            ("Content-Length", str(len(body))),
            ("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"),
        ],
    )
    return [body]


def text_response(start_response, status, text):
    body = text.encode("utf-8")
    start_response(f"{status} {status_text(status)}", [("Content-Type", "text/plain; charset=utf-8")])
    return [body]


def status_text(status):
    return {
        200: "OK",
        202: "Accepted",
        400: "Bad Request",
        404: "Not Found",
        405: "Method Not Allowed",
    }.get(status, "OK")


def read_json(environ):
    try:
        length = int(environ.get("CONTENT_LENGTH") or 0)
    except ValueError:
        length = 0
    if not length:
        return None
    data = environ["wsgi.input"].read(length)
    return json.loads(data.decode("utf-8"))


def serve_file(start_response, path):
    file_path = (ROOT / path.lstrip("/")).resolve()
    if not str(file_path).startswith(str(ROOT)) or not file_path.exists() or not file_path.is_file():
        return text_response(start_response, 404, "Not found")
    content_type = mimetypes.guess_type(str(file_path))[0] or "application/octet-stream"
    body = file_path.read_bytes()
    start_response(
        "200 OK",
        [
            ("Content-Type", content_type),
            ("Content-Length", str(len(body))),
            ("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"),
        ],
    )
    return [body]


def route_static(path):
    if path in ("", "/"):
        return "/index.html"
    if path in ("/subscriptions", "/owner", "/superadmin"):
        return "/subscriptions.html"
    if path == "/promo":
        return "/promo.html"
    if path.startswith("/hotel/") and path.rstrip("/").endswith("/report"):
        return "/report.html"
    if path.startswith("/hotel/"):
        return "/index.html"
    return path


def application(environ, start_response):
    method = environ.get("REQUEST_METHOD", "GET").upper()
    path = urllib.parse.unquote(environ.get("PATH_INFO") or "/")
    query = urllib.parse.parse_qs(environ.get("QUERY_STRING") or "")
    hotel = hotel_slug(query)

    if path.startswith("/api/"):
        if method == "GET":
            return handle_get_api(start_response, path, query, hotel)
        if method in ("POST", "PUT"):
            payload = read_json(environ)
            if payload is None:
                return json_response(start_response, 400, {"error": "Missing JSON body"})
            return handle_write_api(start_response, path, payload, hotel)
        if method == "DELETE":
            return handle_delete_api(start_response, path, query, hotel)
        return json_response(start_response, 405, {"error": "Method not allowed"})

    if method != "GET":
        return text_response(start_response, 405, "Method not allowed")
    return serve_file(start_response, route_static(path))


def handle_get_api(start_response, path, query, hotel):
    if path == "/api/super-hotels":
        with connect() as conn:
            return json_response(start_response, 200, {"superHotels": get_state(conn, "superHotels", [])})
    if path == "/api/orders":
        with connect() as conn:
            return json_response(start_response, 200, {"orders": filter_orders(conn, query)})
    if path == "/api/pms/connection-status":
        with connect() as conn:
            rooms = get_hotel_state(conn, hotel, "rooms", []) or []
            service = PMSService(conn, hotel, rooms)
            return json_response(start_response, 200, service.public_connection_status())
    if path.startswith("/api/pms/reservation/"):
        room_id = path.rsplit("/", 1)[-1]
        with connect() as conn:
            rooms = get_hotel_state(conn, hotel, "rooms", []) or []
            service = PMSService(conn, hotel, rooms)
            try:
                return json_response(start_response, 200, service.get_reservation(room_id))
            except PMSError as error:
                return json_response(start_response, 400, {"error": error.code, "message": error.message})
    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")
            ]
            return json_response(
                start_response,
                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": "Lavina Hotel", "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 json_response(start_response, 404, {"error": "Not found"})


def handle_write_api(start_response, path, payload, hotel):
    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 {})
            return json_response(start_response, 200, {"ok": True, "connection": config})
        if path == "/api/pms/verify-room":
            rooms = get_hotel_state(conn, hotel, "rooms", []) or []
            service = PMSService(conn, hotel, rooms)
            try:
                return json_response(start_response, 200, service.verify_room(payload if isinstance(payload, dict) else {}))
            except PMSError as error:
                return json_response(start_response, 400, {"error": error.code, "message": error.message})
        if path == "/api/pms/post-charge":
            rooms = get_hotel_state(conn, hotel, "rooms", []) or []
            service = PMSService(conn, hotel, rooms)
            try:
                return json_response(start_response, 200, service.post_charge(payload if isinstance(payload, dict) else {}))
            except PMSError as error:
                return json_response(start_response, 400, {"error": error.code, "message": error.message})
        if path == "/api/pms/webhook":
            rooms = get_hotel_state(conn, hotel, "rooms", []) or []
            service = PMSService(conn, hotel, rooms)
            return json_response(start_response, 202, service.store_webhook(payload if isinstance(payload, dict) else {}))
        if path == "/api/super-hotels":
            hotels = payload.get("superHotels") if isinstance(payload, dict) else payload
            if not isinstance(hotels, list):
                return json_response(start_response, 400, {"error": "superHotels payload must be a list"})
            set_state(conn, "superHotels", hotels)
            return json_response(start_response, 200, {"ok": True})
        if path == "/api/orders":
            if not isinstance(payload, list):
                return json_response(start_response, 400, {"error": "Orders payload must be a list"})
            for order in payload:
                order["hotel"] = hotel
                order_id = order.get("id")
                if not order_id:
                    continue
                conn.execute(
                    "insert or replace into orders (id, data) values (?, ?)",
                    (order_id, json.dumps(order, ensure_ascii=False)),
                )
            return json_response(start_response, 200, {"ok": True})
        if path == "/api/staff-accounts":
            if not isinstance(payload, list):
                return json_response(start_response, 400, {"error": "Staff accounts payload must be a list"})
            clean_accounts = [
                {**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)
            return json_response(start_response, 200, {"ok": True})
        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": "Lavina Hotel", "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 [])
            return json_response(start_response, 200, {"ok": True})
    return json_response(start_response, 404, {"error": "Not found"})


def handle_delete_api(start_response, path, query, hotel):
    if path != "/api/orders":
        return json_response(start_response, 404, {"error": "Not found"})
    room = (query.get("room") or [""])[0]
    order_id = (query.get("id") or [""])[0]
    with connect() as conn:
        rows = conn.execute("select id, data from orders").fetchall()
        deleted = 0
        for row in rows:
            order = json.loads(row["data"])
            if str(order.get("hotel") or "default") != hotel:
                continue
            if room and str(order.get("room")) == str(room):
                conn.execute("delete from orders where id = ?", (row["id"],))
                deleted += 1
            elif order_id and row["id"] == order_id:
                conn.execute("delete from orders where id = ?", (row["id"],))
                deleted += 1
        return json_response(start_response, 200, {"ok": True, "deleted": deleted})
