from datetime import datetime, timezone
from uuid import uuid4

from .errors import PMSError, PMSDuplicateCharge
from .mock_connector import MockPMSConnector
from .opera_cloud import OracleOperaCloudConnector, REQUIRED_ENV
from .redaction import safe_json


def now_iso():
    return datetime.now(timezone.utc).isoformat()


class PMSService:
    def __init__(self, conn, hotel="default", rooms=None):
        self.conn = conn
        self.hotel = hotel or "default"
        self.rooms = rooms or []

    def ensure_tables(self):
        ensure_pms_tables(self.conn)

    def get_connection(self):
        self.ensure_tables()
        row = self.conn.execute(
            "select * from pms_connections where hotel = ?", (self.hotel,)
        ).fetchone()
        if not row:
            return {
                "hotel": self.hotel,
                "provider": "mock",
                "mode": "test",
                "property_id": "",
                "hotel_code": self.hotel,
                "enabled": 0,
            }
        data = dict(row)
        data["enabled"] = bool(data.get("enabled"))
        return data

    def save_connection(self, payload):
        self.ensure_tables()
        provider = payload.get("provider") or "mock"
        mode = payload.get("mode") or "test"
        enabled = 1 if payload.get("enabled") else 0
        property_id = payload.get("propertyId") or payload.get("property_id") or ""
        hotel_code = payload.get("hotelCode") or payload.get("hotel_code") or self.hotel
        stamp = now_iso()
        self.conn.execute(
            """
            insert into pms_connections
              (hotel, provider, mode, property_id, hotel_code, enabled, created_at, updated_at)
            values (?, ?, ?, ?, ?, ?, ?, ?)
            on conflict(hotel) do update set
              provider = excluded.provider,
              mode = excluded.mode,
              property_id = excluded.property_id,
              hotel_code = excluded.hotel_code,
              enabled = excluded.enabled,
              updated_at = excluded.updated_at
            """,
            (self.hotel, provider, mode, property_id, hotel_code, enabled, stamp, stamp),
        )
        self.audit("connection_config", "request", provider, "saved", payload)
        return self.get_connection()

    def connector(self):
        config = self.get_connection()
        config["hotel"] = self.hotel
        config["rooms"] = self.rooms
        provider = config.get("provider")
        if provider == "opera_cloud":
            return OracleOperaCloudConnector(config)
        return MockPMSConnector(config)

    def public_connection_status(self):
        config = self.get_connection()
        missing_env = [key for key in REQUIRED_ENV if not __import__("os").environ.get(key)]
        configured = config.get("provider") == "mock" or not missing_env
        return {
            "hotel": self.hotel,
            "provider": config.get("provider"),
            "mode": config.get("mode"),
            "propertyId": config.get("property_id") or "",
            "hotelCode": config.get("hotel_code") or "",
            "enabled": bool(config.get("enabled")),
            "configured": configured,
            "missingEnvironment": [] if config.get("provider") == "mock" else missing_env,
            "message": "Mock PMS is ready" if config.get("provider") == "mock" else "Oracle OHIP configuration required",
        }

    def audit(self, event_type, direction, provider, status, payload=None, message="", error_code=""):
        self.ensure_tables()
        self.conn.execute(
            """
            insert into pms_sync_logs
              (hotel, event_type, direction, provider, request_id, status, message, payload, error_code, attempt_count, created_at)
            values (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?)
            """,
            (
                self.hotel,
                event_type,
                direction,
                provider,
                str(uuid4()),
                status,
                message,
                safe_json(payload or {}),
                error_code,
                now_iso(),
            ),
        )

    def verify_room(self, payload):
        room_id = str(payload.get("roomId") or payload.get("room") or "")
        secure_room_id = payload.get("secureRoomId") or payload.get("secure_room_id")
        connector = self.connector()
        try:
            self.audit("guest_verification", "request", connector.provider, "started", payload)
            result = connector.verifyGuest(room_id, secure_room_id)
            session_id = str(uuid4())
            stamp = now_iso()
            self.conn.execute(
                """
                insert into pms_guest_sessions
                  (id, hotel, room_id, secure_room_id, reservation_id, check_in_status, check_out_status, verified, expires_at, created_at, updated_at)
                values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    session_id,
                    self.hotel,
                    result.get("roomId") or room_id,
                    secure_room_id or "",
                    result.get("reservationId") or "",
                    result.get("checkInStatus") or "",
                    result.get("checkOutStatus") or "",
                    1 if result.get("verified") else 0,
                    result.get("expiresAt") or "",
                    stamp,
                    stamp,
                ),
            )
            response = {
                "verified": bool(result.get("verified")),
                "roomId": result.get("roomId") or room_id,
                "reservationId": result.get("reservationId") or "",
                "checkInStatus": result.get("checkInStatus") or "",
                "checkOutStatus": result.get("checkOutStatus") or "",
                "expiresAt": result.get("expiresAt") or "",
                "guestSessionId": session_id,
            }
            self.audit("guest_verification", "response", connector.provider, "success", response)
            return response
        except PMSError as error:
            self.audit("guest_verification", "response", connector.provider, "failed", payload, error.message, error.code)
            raise

    def get_reservation(self, room_id):
        connector = self.connector()
        try:
            self.audit("reservation_lookup", "request", connector.provider, "started", {"roomId": room_id})
            result = connector.getReservation(room_id)
            self.audit("reservation_lookup", "response", connector.provider, "success", result)
            return result
        except PMSError as error:
            self.audit("reservation_lookup", "response", connector.provider, "failed", {"roomId": room_id}, error.message, error.code)
            raise

    def post_charge(self, payload):
        self.ensure_tables()
        connector = self.connector()
        idempotency_key = payload.get("idempotencyKey") or payload.get("idempotency_key") or payload.get("orderId")
        if not idempotency_key:
            idempotency_key = str(uuid4())
        existing = self.conn.execute(
            "select * from pms_charge_transactions where hotel = ? and idempotency_key = ?",
            (self.hotel, idempotency_key),
        ).fetchone()
        if existing:
            data = dict(existing)
            self.audit("charge_posting", "response", connector.provider, "duplicate", {"idempotencyKey": idempotency_key}, "Duplicate charge prevented", "duplicate_charge")
            return {
                "status": data.get("status"),
                "pmsReference": data.get("pms_reference") or "",
                "duplicate": True,
                "idempotencyKey": idempotency_key,
            }
        charge_id = str(uuid4())
        stamp = now_iso()
        self.conn.execute(
            """
            insert into pms_charge_transactions
              (id, hotel, order_id, idempotency_key, room_id, reservation_id, amount, currency, status, payload, created_at, updated_at)
            values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                charge_id,
                self.hotel,
                payload.get("orderId") or "",
                idempotency_key,
                payload.get("roomId") or payload.get("room") or "",
                payload.get("reservationId") or "",
                float(payload.get("amount") or 0),
                payload.get("currency") or "AED",
                "pending",
                safe_json(payload),
                stamp,
                stamp,
            ),
        )
        try:
            self.audit("charge_posting", "request", connector.provider, "started", payload)
            result = connector.postChargeToRoom({**payload, "idempotencyKey": idempotency_key})
            self.conn.execute(
                "update pms_charge_transactions set status = ?, pms_reference = ?, updated_at = ? where id = ?",
                (result.get("status") or "posted", result.get("pmsReference") or "", now_iso(), charge_id),
            )
            self.audit("charge_posting", "response", connector.provider, "success", result)
            return {**result, "idempotencyKey": idempotency_key, "duplicate": False}
        except PMSError as error:
            self.conn.execute(
                "update pms_charge_transactions set status = ?, error_code = ?, updated_at = ? where id = ?",
                ("queued", error.code, now_iso(), charge_id),
            )
            self.queue_retry("charge_posting", connector.provider, payload, error)
            raise

    def queue_retry(self, event_type, provider, payload, error):
        self.conn.execute(
            """
            insert into pms_sync_logs
              (hotel, event_type, direction, provider, request_id, status, message, payload, error_code, attempt_count, next_retry_at, created_at)
            values (?, ?, 'request', ?, ?, 'queued', ?, ?, ?, 1, datetime('now', '+5 minutes'), ?)
            """,
            (self.hotel, event_type, provider, str(uuid4()), error.message, safe_json(payload), error.code, now_iso()),
        )

    def store_webhook(self, payload):
        self.ensure_tables()
        event_id = payload.get("id") or payload.get("eventId") or str(uuid4())
        event_type = payload.get("eventType") or payload.get("type") or "unknown"
        provider = payload.get("provider") or self.get_connection().get("provider")
        self.conn.execute(
            """
            insert or ignore into pms_webhook_events
              (id, hotel, provider, event_type, payload, processed, created_at)
            values (?, ?, ?, ?, ?, 0, ?)
            """,
            (event_id, self.hotel, provider, event_type, safe_json(payload), now_iso()),
        )
        self.audit("webhook_event", "request", provider, "accepted", payload)
        return {"accepted": True, "eventId": event_id, "eventType": event_type}


def ensure_pms_tables(conn):
    conn.execute(
        """
        create table if not exists pms_connections (
          id integer primary key autoincrement,
          hotel text not null unique,
          provider text not null default 'mock',
          mode text not null default 'test',
          property_id text,
          hotel_code text,
          enabled integer not null default 0,
          created_at text,
          updated_at text
        )
        """
    )
    conn.execute(
        """
        create table if not exists pms_sync_logs (
          id integer primary key autoincrement,
          hotel text not null,
          event_type text not null,
          direction text not null,
          provider text,
          request_id text,
          status text,
          http_status integer,
          message text,
          payload text,
          error_code text,
          attempt_count integer not null default 0,
          next_retry_at text,
          created_at text
        )
        """
    )
    conn.execute(
        """
        create table if not exists pms_guest_sessions (
          id text primary key,
          hotel text not null,
          room_id text not null,
          secure_room_id text,
          reservation_id text,
          check_in_status text,
          check_out_status text,
          verified integer not null default 0,
          expires_at text,
          created_at text,
          updated_at text
        )
        """
    )
    conn.execute(
        """
        create table if not exists pms_charge_transactions (
          id text primary key,
          hotel text not null,
          order_id text,
          idempotency_key text not null unique,
          room_id text,
          reservation_id text,
          amount real not null default 0,
          currency text not null default 'AED',
          status text not null,
          pms_reference text,
          error_code text,
          payload text,
          created_at text,
          updated_at text
        )
        """
    )
    conn.execute(
        """
        create table if not exists pms_webhook_events (
          id text primary key,
          hotel text not null,
          provider text,
          event_type text,
          payload text,
          processed integer not null default 0,
          created_at text,
          processed_at text
        )
        """
    )
