#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from '@modelcontextprotocol/sdk/types.js';
import axios, { AxiosInstance, AxiosError } from 'axios';

// API key from environment variable
const API_KEY = process.env.DUBCO_API_KEY;
if (!API_KEY) {
  throw new Error('DUBCO_API_KEY environment variable is required');
}

// Base URL for Dub.co API
const API_BASE_URL = 'https://api.dub.co';

// Types for Dub.co API
interface Domain {
  id: string;
  slug: string;
  verified: boolean;
  primary: boolean;
  archived: boolean;
  placeholder?: string;
  expiredUrl?: string;
  notFoundUrl?: string;
  logo?: string;
  createdAt: string;
  updatedAt: string;
}

interface Link {
  id: string;
  domain: string;
  key: string;
  url: string;
  shortLink: string;
  // ... other properties
}

interface CreateLinkParams {
  url: string;
  domain?: string;
  key?: string;
  externalId?: string;
  // ... other optional parameters
}

interface UpdateLinkParams {
  url?: string;
  domain?: string;
  key?: string;
  externalId?: string;
  // ... other optional parameters
}

interface ApiErrorResponse {
  error?: string | {
    code?: string;
    message?: string;
    doc_url?: string;
  };
  message?: string;
}

class DubcoServer {
  private server: Server;
  private axiosInstance: AxiosInstance;

  constructor() {
    this.server = new Server(
      {
        name: 'dubco-server',
        version: '1.0.5',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    this.axiosInstance = axios.create({
      baseURL: API_BASE_URL,
      headers: {
        Authorization: `Bearer ${API_KEY}`,
        'Content-Type': 'application/json',
      },
    });

    this.setupToolHandlers();
    
    // Error handling
    this.server.onerror = (error) => console.error('[MCP Error]', error);
    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }

  private setupToolHandlers() {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'create_link',
          description: 'Create a new short link on dub.co, asking the user which domain to use',
          inputSchema: {
            type: 'object',
            properties: {
              url: {
                type: 'string',
                description: 'The destination URL to shorten',
              },
              key: {
                type: 'string',
                description: 'Optional custom slug for the short link. If not provided, a random slug will be generated.',
              },
              externalId: {
                type: 'string',
                description: 'Optional external ID for the link',
              },
              domain: {
                type: 'string',
                description: 'Optional domain slug to use. If not provided, the primary domain will be used.'
              }
            },
            required: ['url'],
          },
        },
        {
          name: 'update_link',
          description: 'Update an existing short link on dub.co',
          inputSchema: {
            type: 'object',
            properties: {
              linkId: {
                type: 'string',
                description: 'The ID of the link to update',
              },
              url: {
                type: 'string',
                description: 'The new destination URL',
              },
              domain: {
                type: 'string',
                description: 'The new domain for the short link',
              },
              key: {
                type: 'string',
                description: 'The new slug for the short link',
              },
            },
            required: ['linkId'],
          },
        },
        {
          name: 'delete_link',
          description: 'Delete a short link on dub.co',
          inputSchema: {
            type: 'object',
            properties: {
              linkId: {
                type: 'string',
                description: 'The ID of the link to delete',
              },
            },
            required: ['linkId'],
          },
        },
      ],
    }));

    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      try {
        switch (request.params.name) {
          case 'create_link':
            return await this.createLink(request.params.arguments);
          case 'update_link':
            return await this.updateLink(request.params.arguments);
          case 'delete_link':
            return await this.deleteLink(request.params.arguments);
          default:
            throw new McpError(
              ErrorCode.MethodNotFound,
              `Unknown tool: ${request.params.name}`
            );
        }
      } catch (error) {
        if (error instanceof McpError) {
          throw error;
        }
        
        if (axios.isAxiosError(error)) {
          const axiosError = error as AxiosError<any>;
          const statusCode = axiosError.response?.status;
          const errorData = axiosError.response?.data;
          
          // Debug logging
          console.error('Error data:', JSON.stringify(errorData));
          
          // Try to extract error message in different ways
          let errorMessage = 'Unknown error';
          if (errorData) {
            if (typeof errorData === 'string') {
              errorMessage = errorData;
            } else if (errorData.error) {
              // Handle nested error object from Dub.co API
              if (typeof errorData.error === 'object' && errorData.error.message) {
                errorMessage = errorData.error.message;
              } else {
                errorMessage = errorData.error;
              }
            } else if (errorData.message) {
              errorMessage = errorData.message;
            } else {
              errorMessage = JSON.stringify(errorData);
            }
          } else {
            errorMessage = axiosError.message;
          }
          
          return {
            content: [
              {
                type: 'text',
                text: `Error: ${statusCode} - ${errorMessage}`,
              },
            ],
            isError: true,
          };
        }
        
        throw new McpError(
          ErrorCode.InternalError,
          `Unexpected error: ${(error as Error).message}`
        );
      }
    });
  }

  private async getDomains(): Promise<Domain[]> {
    try {
      const response = await this.axiosInstance.get('/domains');
      return response.data;
    } catch (error) {
      console.error('Error fetching domains:', error);
      throw error;
    }
  }

  private async getPrimaryDomain(): Promise<Domain> {
    const domains = await this.getDomains();
    
    if (domains.length === 0) {
      throw new McpError(
        ErrorCode.InvalidRequest,
        'No domains available in your workspace'
      );
    }
    
    // Find the primary domain or use the first one
    const primaryDomain = domains.find(domain => domain.primary) || domains[0];
    return primaryDomain;
  }

  private async getDomainBySlug(slug: string): Promise<Domain | undefined> {
    const domains = await this.getDomains();
    return domains.find(domain => domain.slug === slug);
  }

  private async createLink(args: any): Promise<any> {
    if (!args.url) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'URL is required'
      );
    }

    try {
      // Determine which domain to use
      let domain: Domain;
      
      if (args.domain) {
        // If domain is specified, try to find it
        const foundDomain = await this.getDomainBySlug(args.domain);
        if (!foundDomain) {
          return {
            content: [
              {
                type: 'text',
                text: `Domain "${args.domain}" not found. Using primary domain instead.`,
              },
            ],
            isError: false,
          };
        }
        domain = foundDomain;
      } else {
        // Otherwise use the primary domain
        domain = await this.getPrimaryDomain();
      }
      
      // Create the link with the selected domain
      const createParams: CreateLinkParams = {
        url: args.url,
        domain: domain.slug,
      };
      
      if (args.key) {
        createParams.key = args.key;
      }
      
      if (args.externalId) {
        createParams.externalId = args.externalId;
      }
      
      const response = await this.axiosInstance.post('/links', createParams);
      const link = response.data;
      
      return {
        content: [
          {
            type: 'text',
            text: `Short link created: ${link.shortLink}\n\nDestination: ${link.url}\nID: ${link.id}`,
          },
        ],
      };
    } catch (error) {
      if (axios.isAxiosError(error)) {
        const axiosError = error as AxiosError<any>;
        const statusCode = axiosError.response?.status;
        const errorData = axiosError.response?.data;
        
        // Debug logging
        console.error('Error data:', JSON.stringify(errorData));
        
        // Try to extract error message in different ways
        let errorMessage = 'Unknown error';
        if (errorData) {
          if (typeof errorData === 'string') {
            errorMessage = errorData;
          } else if (errorData.error) {
            // Handle nested error object from Dub.co API
            if (typeof errorData.error === 'object' && errorData.error.message) {
              errorMessage = errorData.error.message;
            } else {
              errorMessage = errorData.error;
            }
          } else if (errorData.message) {
            errorMessage = errorData.message;
          } else {
            errorMessage = JSON.stringify(errorData);
          }
        } else {
          errorMessage = axiosError.message;
        }
        
        return {
          content: [
            {
              type: 'text',
              text: `Error creating link: ${statusCode} - ${errorMessage}`,
            },
          ],
          isError: true,
        };
      }
      
      return {
        content: [
          {
            type: 'text',
            text: `Error creating link: ${(error as Error).message}`,
          },
        ],
        isError: true,
      };
    }
  }

  private async updateLink(args: any): Promise<any> {
    if (!args.linkId) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Link ID is required'
      );
    }

    // Prepare update parameters
    const updateParams: UpdateLinkParams = {};
    
    if (args.url) {
      updateParams.url = args.url;
    }
    
    if (args.domain) {
      updateParams.domain = args.domain;
    }
    
    if (args.key) {
      updateParams.key = args.key;
    }
    
    if (Object.keys(updateParams).length === 0) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'At least one parameter to update is required'
      );
    }
    
    try {
      const response = await this.axiosInstance.patch(`/links/${args.linkId}`, updateParams);
      const link = response.data;
      
      return {
        content: [
          {
            type: 'text',
            text: `Link updated: ${link.shortLink}\n\nDestination: ${link.url}\nID: ${link.id}`,
          },
        ],
      };
    } catch (error) {
      if (axios.isAxiosError(error)) {
        const axiosError = error as AxiosError<any>;
        const statusCode = axiosError.response?.status;
        const errorData = axiosError.response?.data;
        
        // Debug logging
        console.error('Error data:', JSON.stringify(errorData));
        
        // Try to extract error message in different ways
        let errorMessage = 'Unknown error';
        if (errorData) {
          if (typeof errorData === 'string') {
            errorMessage = errorData;
          } else if (errorData.error) {
            // Handle nested error object from Dub.co API
            if (typeof errorData.error === 'object' && errorData.error.message) {
              errorMessage = errorData.error.message;
            } else {
              errorMessage = errorData.error;
            }
          } else if (errorData.message) {
            errorMessage = errorData.message;
          } else {
            errorMessage = JSON.stringify(errorData);
          }
        } else {
          errorMessage = axiosError.message;
        }
        
        return {
          content: [
            {
              type: 'text',
              text: `Error updating link: ${statusCode} - ${errorMessage}`,
            },
          ],
          isError: true,
        };
      }
      
      throw error;
    }
  }


  private async deleteLink(args: any): Promise<any> {
    if (!args.linkId) {
      throw new McpError(
        ErrorCode.InvalidParams,
        'Link ID is required'
      );
    }
    
    try {
      const response = await this.axiosInstance.delete(`/links/${args.linkId}`);
      
      return {
        content: [
          {
            type: 'text',
            text: `Link with ID ${args.linkId} has been deleted.`,
          },
        ],
      };
    } catch (error) {
      if (axios.isAxiosError(error)) {
        const axiosError = error as AxiosError<any>;
        const statusCode = axiosError.response?.status;
        const errorData = axiosError.response?.data;
        
        // Debug logging
        console.error('Error data:', JSON.stringify(errorData));
        
        // Try to extract error message in different ways
        let errorMessage = 'Unknown error';
        if (errorData) {
          if (typeof errorData === 'string') {
            errorMessage = errorData;
          } else if (errorData.error) {
            // Handle nested error object from Dub.co API
            if (typeof errorData.error === 'object' && errorData.error.message) {
              errorMessage = errorData.error.message;
            } else {
              errorMessage = errorData.error;
            }
          } else if (errorData.message) {
            errorMessage = errorData.message;
          } else {
            errorMessage = JSON.stringify(errorData);
          }
        } else {
          errorMessage = axiosError.message;
        }
        
        return {
          content: [
            {
              type: 'text',
              text: `Error deleting link: ${statusCode} - ${errorMessage}`,
            },
          ],
          isError: true,
        };
      }
      
      throw error;
    }
  }

  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Dub.co MCP server running on stdio');
  }
}

const server = new DubcoServer();
server.run().catch(console.error);
