#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

// ============================================
// CONFIGURATION
// ============================================
const MCP_NAME = "rippling";
const MCP_VERSION = "1.0.0";

// Rippling API base URL
const API_BASE_URL = "https://api.rippling.com/platform/api";

// ============================================
// API CLIENT
// ============================================
class RipplingClient {
  private apiKey: string;
  private baseUrl: string;

  constructor(apiKey: string) {
    this.apiKey = apiKey;
    this.baseUrl = API_BASE_URL;
  }

  async request(endpoint: string, options: RequestInit = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const response = await fetch(url, {
      ...options,
      headers: {
        "Authorization": `Bearer ${this.apiKey}`,
        "Content-Type": "application/json",
        ...options.headers,
      },
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Rippling API error: ${response.status} ${response.statusText} - ${errorText}`);
    }

    return response.json();
  }

  async get(endpoint: string) {
    return this.request(endpoint, { method: "GET" });
  }

  async post(endpoint: string, data: any) {
    return this.request(endpoint, {
      method: "POST",
      body: JSON.stringify(data),
    });
  }
}

// ============================================
// TOOL DEFINITIONS
// ============================================
const tools = [
  {
    name: "list_employees",
    description: "List employees in the organization. Returns employee details including name, email, department, and employment status.",
    inputSchema: {
      type: "object" as const,
      properties: {
        limit: { type: "number", description: "Max employees to return (default 100, max 1000)" },
        offset: { type: "number", description: "Pagination offset" },
        include_terminated: { type: "boolean", description: "Include terminated employees (default false)" },
      },
    },
  },
  {
    name: "get_employee",
    description: "Get detailed information about a specific employee including personal info, employment details, and manager.",
    inputSchema: {
      type: "object" as const,
      properties: {
        employee_id: { type: "string", description: "Employee ID (Rippling unique identifier)" },
      },
      required: ["employee_id"],
    },
  },
  {
    name: "list_departments",
    description: "List all departments in the organization with their names and hierarchy.",
    inputSchema: {
      type: "object" as const,
      properties: {
        limit: { type: "number", description: "Max departments to return" },
        offset: { type: "number", description: "Pagination offset" },
      },
    },
  },
  {
    name: "list_teams",
    description: "List all teams in the organization. Teams are groups of employees that can span departments.",
    inputSchema: {
      type: "object" as const,
      properties: {
        limit: { type: "number", description: "Max teams to return" },
        offset: { type: "number", description: "Pagination offset" },
      },
    },
  },
  {
    name: "get_payroll",
    description: "Get payroll information and pay runs. Requires payroll read permissions.",
    inputSchema: {
      type: "object" as const,
      properties: {
        employee_id: { type: "string", description: "Filter by specific employee ID" },
        start_date: { type: "string", description: "Filter pay runs starting on or after (YYYY-MM-DD)" },
        end_date: { type: "string", description: "Filter pay runs ending on or before (YYYY-MM-DD)" },
      },
    },
  },
  {
    name: "list_devices",
    description: "List devices managed by Rippling IT. Includes computers, phones, and other equipment assigned to employees.",
    inputSchema: {
      type: "object" as const,
      properties: {
        limit: { type: "number", description: "Max devices to return" },
        offset: { type: "number", description: "Pagination offset" },
        employee_id: { type: "string", description: "Filter by assigned employee" },
        device_type: { type: "string", description: "Filter by type: laptop, desktop, phone, tablet" },
      },
    },
  },
  {
    name: "list_apps",
    description: "List applications integrated with Rippling. Shows apps available for provisioning to employees.",
    inputSchema: {
      type: "object" as const,
      properties: {
        limit: { type: "number", description: "Max apps to return" },
        offset: { type: "number", description: "Pagination offset" },
      },
    },
  },
  {
    name: "get_company",
    description: "Get information about the current company including name, EIN, and settings.",
    inputSchema: {
      type: "object" as const,
      properties: {},
    },
  },
  {
    name: "list_groups",
    description: "List custom groups defined in Rippling. Groups can be used for access control and app provisioning.",
    inputSchema: {
      type: "object" as const,
      properties: {},
    },
  },
  {
    name: "list_levels",
    description: "List job levels defined in the organization (e.g., IC1, IC2, Manager, Director).",
    inputSchema: {
      type: "object" as const,
      properties: {
        limit: { type: "number", description: "Max levels to return" },
        offset: { type: "number", description: "Pagination offset" },
      },
    },
  },
  {
    name: "list_work_locations",
    description: "List work locations/offices defined in the organization.",
    inputSchema: {
      type: "object" as const,
      properties: {
        limit: { type: "number", description: "Max locations to return" },
        offset: { type: "number", description: "Pagination offset" },
      },
    },
  },
  {
    name: "get_leave_requests",
    description: "Get leave/time-off requests. Filter by employee, status, or date range.",
    inputSchema: {
      type: "object" as const,
      properties: {
        employee_id: { type: "string", description: "Filter by employee ID" },
        status: { type: "string", description: "Filter by status: pending, approved, denied, cancelled" },
        start_date: { type: "string", description: "Filter leave starting on or after (YYYY-MM-DD)" },
        end_date: { type: "string", description: "Filter leave ending on or before (YYYY-MM-DD)" },
      },
    },
  },
];

// ============================================
// TOOL HANDLERS
// ============================================
async function handleTool(client: RipplingClient, name: string, args: any) {
  switch (name) {
    case "list_employees": {
      const { limit = 100, offset = 0, include_terminated = false } = args;
      const params = new URLSearchParams();
      params.append("limit", String(Math.min(limit, 1000)));
      params.append("offset", String(offset));
      
      const endpoint = include_terminated 
        ? `/employees?${params}&includeTerminated=true`
        : `/employees?${params}`;
      
      return await client.get(endpoint);
    }
    
    case "get_employee": {
      const { employee_id } = args;
      return await client.get(`/employees/${employee_id}`);
    }
    
    case "list_departments": {
      const { limit = 100, offset = 0 } = args;
      const params = new URLSearchParams();
      params.append("limit", String(limit));
      params.append("offset", String(offset));
      return await client.get(`/departments?${params}`);
    }
    
    case "list_teams": {
      const { limit = 100, offset = 0 } = args;
      const params = new URLSearchParams();
      params.append("limit", String(limit));
      params.append("offset", String(offset));
      return await client.get(`/teams?${params}`);
    }
    
    case "get_payroll": {
      const { employee_id, start_date, end_date } = args;
      const params = new URLSearchParams();
      if (employee_id) params.append("employeeId", employee_id);
      if (start_date) params.append("startDate", start_date);
      if (end_date) params.append("endDate", end_date);
      
      const query = params.toString();
      return await client.get(`/payroll${query ? `?${query}` : ""}`);
    }
    
    case "list_devices": {
      const { limit = 100, offset = 0, employee_id, device_type } = args;
      const params = new URLSearchParams();
      params.append("limit", String(limit));
      params.append("offset", String(offset));
      if (employee_id) params.append("employeeId", employee_id);
      if (device_type) params.append("deviceType", device_type);
      
      return await client.get(`/devices?${params}`);
    }
    
    case "list_apps": {
      const { limit = 100, offset = 0 } = args;
      const params = new URLSearchParams();
      params.append("limit", String(limit));
      params.append("offset", String(offset));
      return await client.get(`/apps?${params}`);
    }
    
    case "get_company": {
      return await client.get("/companies/current");
    }
    
    case "list_groups": {
      return await client.get("/groups");
    }
    
    case "list_levels": {
      const { limit = 100, offset = 0 } = args;
      const params = new URLSearchParams();
      params.append("limit", String(limit));
      params.append("offset", String(offset));
      return await client.get(`/levels?${params}`);
    }
    
    case "list_work_locations": {
      const { limit = 100, offset = 0 } = args;
      const params = new URLSearchParams();
      params.append("limit", String(limit));
      params.append("offset", String(offset));
      return await client.get(`/work-locations?${params}`);
    }
    
    case "get_leave_requests": {
      const { employee_id, status, start_date, end_date } = args;
      const params = new URLSearchParams();
      if (employee_id) params.append("requestedBy", employee_id);
      if (status) params.append("status", status);
      if (start_date) params.append("from", start_date);
      if (end_date) params.append("to", end_date);
      
      const query = params.toString();
      return await client.get(`/leave-requests${query ? `?${query}` : ""}`);
    }
    
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
}

// ============================================
// SERVER SETUP
// ============================================
async function main() {
  const apiKey = process.env.RIPPLING_API_KEY;
  
  if (!apiKey) {
    console.error("Error: RIPPLING_API_KEY environment variable required");
    process.exit(1);
  }

  const client = new RipplingClient(apiKey);

  const server = new Server(
    { name: `${MCP_NAME}-mcp`, version: MCP_VERSION },
    { capabilities: { tools: {} } }
  );

  server.setRequestHandler(ListToolsRequestSchema, async () => ({
    tools,
  }));

  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;
    
    try {
      const result = await handleTool(client, name, args || {});
      return {
        content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return {
        content: [{ type: "text", text: `Error: ${message}` }],
        isError: true,
      };
    }
  });

  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error(`${MCP_NAME} MCP server running on stdio`);
}

main().catch(console.error);
