#!/usr/bin/env python3
"""
Lodgify MCP Server

A Model Context Protocol server for interacting with the Lodgify vacation rental API.
Provides tools and resources for managing properties, bookings, rates, and calendar data.

Usage:
    uv add "mcp[cli]" httpx
    mcp install lodgify_server.py --name "Lodgify API" -v LODGIFY_API_KEY=your_api_key_here
"""

import os
import sys
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from typing import Any

import httpx
from mcp.server.fastmcp import Context, FastMCP  # type: ignore[import-not-found]
from mcp.server.fastmcp.prompts import base  # type: ignore[import-not-found]

# Constants
HTTP_OK = 200
PROPERTIES_SUMMARY_LIMIT = 10


@dataclass
class LodgifyConfig:
    """Configuration for Lodgify API access."""
    api_key: str
    base_url: str = "https://api.lodgify.com/v2"
    timeout: int = 30


@dataclass
class AppContext:
    """Application context with Lodgify client."""
    config: LodgifyConfig
    client: httpx.AsyncClient


@asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
    """Manage application lifecycle with Lodgify API client."""
    api_key = os.getenv("LODGIFY_API_KEY")
    if not api_key:
        raise ValueError("LODGIFY_API_KEY environment variable is required")

    config = LodgifyConfig(api_key=api_key)

    # Create HTTP client with proper headers
    client = httpx.AsyncClient(
        base_url=config.base_url,
        headers={
            "X-ApiKey": config.api_key,
            "Accept": "application/json",
            "Content-Type": "application/json"
        },
        timeout=config.timeout
    )

    try:
        # Test API connection
        server.info("Testing Lodgify API connection...")  # type: ignore[attr-defined]
        response = await client.get("/properties", params={"limit": 1})
        if response.status_code == HTTP_OK:
            server.info("✅ Lodgify API connection successful")  # type: ignore[attr-defined]
        else:
            server.warning(f"⚠️ API test returned status {response.status_code}")  # type: ignore[attr-defined]

        yield AppContext(config=config, client=client)
    finally:
        await client.aclose()


# Create the MCP server
mcp = FastMCP(
    "Lodgify API Server",
    dependencies=["httpx>=0.25.0"],
    lifespan=app_lifespan
)


async def handle_api_error(response: httpx.Response) -> str:
    """Handle API errors and return user-friendly messages."""
    try:
        error_data = response.json()
        if isinstance(error_data, dict):
            message = error_data.get("message", f"API Error {response.status_code}")
            code = error_data.get("code", response.status_code)
            return f"Lodgify API Error {code}: {message}"
    except Exception:
        pass

    return f"HTTP {response.status_code}: {response.text[:200]}..."


# Global client reference - will be set during lifespan
_client: httpx.AsyncClient | None = None


def get_client() -> httpx.AsyncClient:
    """Get the global HTTP client."""
    if _client is None:
        raise RuntimeError("Client not initialized")
    return _client


# Update the lifespan to set global client
@asynccontextmanager
async def app_lifespan_with_global(server: FastMCP) -> AsyncIterator[AppContext]:
    """Manage application lifecycle with Lodgify API client."""
    global _client  # noqa: PLW0603

    api_key = os.getenv("LODGIFY_API_KEY")
    if not api_key:
        raise ValueError("LODGIFY_API_KEY environment variable is required")

    config = LodgifyConfig(api_key=api_key)

    # Create HTTP client with proper headers
    _client = httpx.AsyncClient(
        base_url=config.base_url,        headers={
            "X-ApiKey": config.api_key,
            "Accept": "application/json",
            "Content-Type": "application/json"
        },
        timeout=config.timeout
    )

    try:
        # Test API connection
        print("Testing Lodgify API connection...", file=sys.stderr)
        response = await _client.get("/properties", params={"limit": 1})
        if response.status_code == HTTP_OK:
            print("Lodgify API connection successful", file=sys.stderr)
        else:
            print(f"API test returned status {response.status_code}", file=sys.stderr)

        yield AppContext(config=config, client=_client)
    finally:
        await _client.aclose()
        _client = None


# Recreate server with updated lifespan
mcp = FastMCP(
    "Lodgify API Server",
    dependencies=["httpx>=0.25.0"],
    lifespan=app_lifespan_with_global
)


# RESOURCES - Expose data for LLM context

@mcp.resource("lodgify://properties")
async def get_properties_list() -> str:
    """Get a summary list of all properties."""
    client = get_client()

    try:
        response = await client.get("/properties")
        response.raise_for_status()

        properties = response.json()
        if isinstance(properties, dict) and "items" in properties:
            properties_data = properties["items"]
        else:
            properties_data = properties

        # Format for LLM consumption
        summary = ["# Lodgify Properties Summary\n"]

        for prop in properties_data[:PROPERTIES_SUMMARY_LIMIT]:  # Limit to first 10 for overview
            summary.append(f"## Property ID: {prop.get('id', 'N/A')}")
            summary.append(f"- **Name**: {prop.get('name', 'N/A')}")
            summary.append(f"- **Type**: {prop.get('property_type', 'N/A')}")
            summary.append(f"- **Status**: {prop.get('status', 'N/A')}")
            summary.append(f"- **Max Guests**: {prop.get('max_guests', 'N/A')}")
            summary.append(f"- **Bedrooms**: {prop.get('bedrooms', 'N/A')}")
            summary.append("")

        if len(properties_data) > PROPERTIES_SUMMARY_LIMIT:
            summary.append(f"... and {len(properties_data) - PROPERTIES_SUMMARY_LIMIT} more properties.")

        return "\n".join(summary)

    except httpx.HTTPStatusError as e:
        return await handle_api_error(e.response)
    except Exception as e:
        return f"Error fetching properties: {str(e)}"


@mcp.resource("lodgify://property/{property_id}")
async def get_property_details(property_id: str) -> str:
    """Get detailed information about a specific property."""
    client = get_client()

    try:
        response = await client.get(f"/properties/{property_id}")
        response.raise_for_status()

        property_data = response.json()

        # Format property details for LLM
        details = [f"# Property Details: {property_data.get('name', 'N/A')}\n"]
        details.append(f"**Property ID**: {property_data.get('id', 'N/A')}")
        details.append(f"**Type**: {property_data.get('property_type', 'N/A')}")
        details.append(f"**Status**: {property_data.get('status', 'N/A')}")
        details.append(f"**Maximum Guests**: {property_data.get('max_guests', 'N/A')}")
        details.append(f"**Bedrooms**: {property_data.get('bedrooms', 'N/A')}")
        details.append(f"**Bathrooms**: {property_data.get('bathrooms', 'N/A')}")

        if property_data.get('description'):
            details.append(f"\n**Description**: {property_data['description']}")

        if property_data.get('address'):
            details.append(f"\n**Address**: {property_data['address']}")

        # Add room types if available
        if property_data.get('room_types'):
            details.append("\n## Room Types:")
            for room in property_data['room_types']:
                details.append(f"- **{room.get('name', 'N/A')}** (ID: {room.get('id', 'N/A')})")
                details.append(f"  - Max Guests: {room.get('max_guests', 'N/A')}")
                details.append(f"  - Base Rate: {room.get('base_rate', 'N/A')}")

        return "\n".join(details)

    except httpx.HTTPStatusError as e:
        return await handle_api_error(e.response)
    except Exception as e:
        return f"Error fetching property {property_id}: {str(e)}"


@mcp.resource("lodgify://bookings/recent")
async def get_recent_bookings() -> str:
    """Get recent bookings summary."""
    client = get_client()

    try:
        response = await client.get("/reservations/bookings", params={"size": 20})
        response.raise_for_status()

        bookings_data = response.json()
        if isinstance(bookings_data, dict) and "items" in bookings_data:
            bookings = bookings_data["items"]
        else:
            bookings = bookings_data

        summary = ["# Recent Bookings Summary\n"]

        for booking in bookings:
            summary.append(f"## Booking ID: {booking.get('id', 'N/A')}")
            summary.append(f"- **Guest**: {booking.get('guest_name', 'N/A')}")
            summary.append(f"- **Property**: {booking.get('property_name', booking.get('property_id', 'N/A'))}")
            summary.append(f"- **Arrival**: {booking.get('arrival', 'N/A')}")
            summary.append(f"- **Departure**: {booking.get('departure', 'N/A')}")
            summary.append(f"- **Status**: {booking.get('status', 'N/A')}")
            summary.append(f"- **Total**: {booking.get('total_amount', 'N/A')} {booking.get('currency_code', '')}")
            summary.append("")

        return "\n".join(summary)

    except httpx.HTTPStatusError as e:
        return await handle_api_error(e.response)
    except Exception as e:
        return f"Error fetching bookings: {str(e)}"


# TOOLS - Execute actions

@mcp.tool()
async def get_properties(
    ctx: Context,
    limit: int = 50,
    offset: int = 0,
    status: str | None = None
) -> dict[str, Any]:
    """
    Get a list of properties with optional filtering.

    Args:
        limit: Maximum number of properties to return (default: 50)
        offset: Number of properties to skip (default: 0)
        status: Filter by property status (e.g., "Active", "Inactive")
    """
    client = get_client()

    params: dict[str, Any] = {"limit": limit, "offset": offset}
    if status:
        params["status"] = status

    try:
        response = await client.get("/properties", params=params)
        response.raise_for_status()

        return {
            "success": True,
            "data": response.json(),
            "message": f"Retrieved {limit} properties (offset: {offset})"
        }

    except httpx.HTTPStatusError as e:
        error_msg = await handle_api_error(e.response)
        return {"success": False, "error": error_msg}
    except Exception as e:
        return {"success": False, "error": str(e)}


@mcp.tool()
async def get_property_by_id(ctx: Context, property_id: int) -> dict[str, Any]:
    """
    Get detailed information about a specific property.

    Args:
        property_id: The unique ID of the property
    """
    client = get_client()

    try:
        response = await client.get(f"/properties/{property_id}")
        response.raise_for_status()

        return {
            "success": True,
            "data": response.json(),
            "message": f"Retrieved property {property_id}"
        }

    except httpx.HTTPStatusError as e:
        error_msg = await handle_api_error(e.response)
        return {"success": False, "error": error_msg}
    except Exception as e:
        return {"success": False, "error": str(e)}


@mcp.tool()
async def get_bookings(
    ctx: Context,
    size: int = 50,
    page: int = 1,
    property_id: int | None = None,
    status: str | None = None,
    start_date: str | None = None,
    end_date: str | None = None
) -> dict[str, Any]:
    """
    Get bookings with optional filtering.

    Args:
        size: Maximum number of bookings to return
        page: Number of page to get
        property_id: Filter by specific property ID
        status: Filter by booking status (e.g., "Booked", "Cancelled")
        start_date: Filter bookings from this date (YYYY-MM-DD)
    end_date: Filter bookings until this date (YYYY-MM-DD)
    """
    client = get_client()

    params: dict[str, Any] = {"size": size, "page": page }
    if property_id:
        params["property_id"] = property_id
    if status:
        params["status"] = status
    if start_date:
        params["start_date"] = start_date
    if end_date:
        params["end_date"] = end_date

    try:
        response = await client.get("/reservations/bookings", params=params)
        response.raise_for_status()

        return {
            "success": True,
            "data": response.json(),
            "message": f"Retrieved bookings with filters: {params}"
        }

    except httpx.HTTPStatusError as e:
        error_msg = await handle_api_error(e.response)
        return {"success": False, "error": error_msg}
    except Exception as e:
        return {"success": False, "error": str(e)}


@mcp.tool()
async def create_booking(
    ctx: Context,
    property_id: int,
    room_type_id: int,
    arrival: str,
    departure: str,
    guest_name: str,
    guest_email: str,
    guest_phone: str,
    guest_country_code: str,
    people: int = 2,
    total: float = 0.0,
    currency_code: str = "USD",
    status: str = "Booked",
    source_text: str = "MCP API"
) -> dict[str, Any]:
    """
    Create a new booking.

    Args:
        property_id: The property ID for the booking
        room_type_id: The room type ID within the property
        arrival: Arrival date (YYYY-MM-DD)
        departure: Departure date (YYYY-MM-DD)
        guest_name: Guest's full name
        guest_email: Guest's email address
        guest_phone: Guest's phone number
        guest_country_code: Guest's country code (e.g., "US", "CA")
        people: Number of people (default: 2)
        total: Total booking amount (default: 0.0)
        currency_code: Currency code (default: "USD")
        status: Booking status (default: "Booked")
        source_text: Source description (default: "MCP API")
    """
    client = get_client()

    booking_data = {
        "guest": {
            "name": guest_name,
            "email": guest_email,
            "phone": guest_phone,
            "country_code": guest_country_code
        },
        "status": status,
        "property_id": property_id,
        "arrival": arrival,
        "departure": departure,
        "bookability": "InstantBooking",
        "origin": "manual",
        "total": total,
        "currency_code": currency_code,
        "source_text": source_text,
        "rooms": [
            {
                "room_type_id": room_type_id,
                "people": people,
                "key_code": ""
            }
        ]
    }

    try:
        response = await client.post("/reservation/booking", json=booking_data)
        response.raise_for_status()

        return {
            "success": True,
            "data": response.json(),
            "message": f"Created booking for {guest_name} at property {property_id}"
        }

    except httpx.HTTPStatusError as e:
        error_msg = await handle_api_error(e.response)
        return {"success": False, "error": error_msg}
    except Exception as e:
        return {"success": False, "error": str(e)}


@mcp.tool()
async def get_calendar(
    ctx: Context,
    property_id: int,
    room_type_id: int | None = None,
    start_date: str | None = None,
    end_date: str | None = None
) -> dict[str, Any]:
    """
    Get calendar/availability information for a property.

    Args:
        property_id: The property ID
        room_type_id: Optional room type ID within the property
        start_date: Start date for calendar (YYYY-MM-DD)
        end_date: End date for calendar (YYYY-MM-DD)
    """
    client = get_client()

    params: dict[str, Any] = {"HouseId": property_id}
    if room_type_id:
        params["RoomTypeId"] = room_type_id
    if start_date:
        params["StartDate"] = start_date
    if end_date:
        params["EndDate"] = end_date

    try:
        response = await client.get("/rates/calendar", params=params)
        response.raise_for_status()

        return {
            "success": True,
            "data": response.json(),
            "message": f"Retrieved calendar for property {property_id}"
        }

    except httpx.HTTPStatusError as e:
        error_msg = await handle_api_error(e.response)
        return {"success": False, "error": error_msg}
    except Exception as e:
        return {"success": False, "error": str(e)}


@mcp.tool()
async def get_booking_by_id(ctx: Context, booking_id: int) -> dict[str, Any]:
    """
    Get detailed information about a specific booking.

    Args:
        booking_id: The unique ID of the booking
    """
    client = get_client()

    try:
        response = await client.get(f"/reservations/bookings/{booking_id}")
        response.raise_for_status()

        return {
            "success": True,
            "data": response.json(),
            "message": f"Retrieved booking {booking_id}"
        }

    except httpx.HTTPStatusError as e:
        error_msg = await handle_api_error(e.response)
        return {"success": False, "error": error_msg}
    except Exception as e:
        return {"success": False, "error": str(e)}


@mcp.tool()
async def update_booking_status(
    ctx: Context,
    booking_id: int,
    status: str
) -> dict[str, Any]:
    """
    Update the status of an existing booking.

    Args:
        booking_id: The unique ID of the booking
        status: New status (e.g., "Booked", "Cancelled", "CheckedIn", "CheckedOut")
    """
    client = get_client()

    try:
        # First get the current booking data
        get_response = await client.get(f"/reservations/bookings/{booking_id}")
        get_response.raise_for_status()
        booking_data = get_response.json()

        # Update only the status
        booking_data["status"] = status

        # Send the update
        response = await client.put(f"/reservations/bookings/{booking_id}", json=booking_data)
        response.raise_for_status()

        return {
            "success": True,
            "data": response.json(),
            "message": f"Updated booking {booking_id} status to {status}"
        }

    except httpx.HTTPStatusError as e:
        error_msg = await handle_api_error(e.response)
        return {"success": False, "error": error_msg}
    except Exception as e:
        return {"success": False, "error": str(e)}


# PROMPTS - Interactive templates

@mcp.prompt()
def analyze_lodgify_data() -> list[base.Message]:
    """Analyze Lodgify property and booking data for insights."""
    return [
        base.UserMessage(
            "I'd like to analyze my Lodgify data. Please help me understand:\n"
            "1. Property performance and occupancy rates\n"
            "2. Booking trends and patterns\n"
            "3. Revenue optimization opportunities\n"
            "4. Guest demographics and preferences\n\n"
            "Use the available Lodgify tools to gather current data and provide insights."
        )
    ]


@mcp.prompt()
def create_booking_workflow() -> list[base.Message]:
    """Guide through creating a new booking in Lodgify."""
    return [
        base.UserMessage(
            "I need to create a new booking in Lodgify. Please guide me through:\n"
            "1. First, show me available properties\n"
            "2. Help me select the right property and room type\n"
            "3. Check availability for my desired dates\n"
            "4. Collect guest information and create the booking\n\n"
            "Let's start by getting the list of available properties."
        )
    ]


@mcp.prompt()
def property_management_review() -> list[base.Message]:
    """Review property management and booking status."""
    return [
        base.UserMessage(
            "Please provide a comprehensive review of my Lodgify account:\n"
            "1. Overview of all properties and their current status\n"
            "2. Recent bookings and their details\n"
            "3. Any upcoming check-ins or check-outs\n"
            "4. Properties that might need attention\n\n"
            "Use the Lodgify resources and tools to gather this information."
        )
    ]


def main() -> None:
    """Run the Lodgify MCP server."""
    mcp.run()
if __name__ == "__main__":
    main()
