import { OpenAPIV3 } from 'openapi-types'
import { describe, it, expect } from 'vitest'
import { OpenAPIToMCPConverter } from '../parser'

describe('OpenAPI Multipart Form Parser', () => {
  it('converts single file upload endpoint to tool', () => {
    const spec: OpenAPIV3.Document = {
      openapi: '3.0.0',
      info: { title: 'Test API', version: '1.0.0' },
      paths: {
        '/pets/{id}/photo': {
          post: {
            operationId: 'uploadPetPhoto',
            summary: 'Upload a photo for a pet',
            parameters: [
              {
                name: 'id',
                in: 'path',
                required: true,
                schema: { type: 'integer' },
              },
            ],
            requestBody: {
              required: true,
              content: {
                'multipart/form-data': {
                  schema: {
                    type: 'object',
                    required: ['photo'],
                    properties: {
                      photo: {
                        type: 'string',
                        format: 'binary',
                        description: 'The photo to upload',
                      },
                      caption: {
                        type: 'string',
                        description: 'Optional caption for the photo',
                      },
                    },
                  },
                },
              },
            },
            responses: {
              '201': {
                description: 'Photo uploaded successfully',
              },
            },
          },
        },
      },
    }

    const converter = new OpenAPIToMCPConverter(spec)
    const { tools } = converter.convertToMCPTools()
    expect(Object.keys(tools)).toHaveLength(1)

    const [tool] = Object.values(tools)
    expect(tool.methods).toHaveLength(1)
    const [method] = tool.methods
    expect(method.name).toBe('uploadPetPhoto')
    expect(method.description).toContain('Upload a photo for a pet')

    // Check parameters
    expect(method.inputSchema.properties).toEqual({
      id: {
        type: 'integer',
      },
      photo: {
        type: 'string',
        format: 'uri-reference',
        description: expect.stringContaining('The photo to upload (absolute paths to local files)'),
      },
      caption: {
        type: 'string',
        description: expect.stringContaining('Optional caption'),
      },
    })

    expect(method.inputSchema.required).toContain('id')
    expect(method.inputSchema.required).toContain('photo')
    expect(method.inputSchema.required).not.toContain('caption')
  })

  it('converts multiple file upload endpoint to tool', () => {
    const spec: OpenAPIV3.Document = {
      openapi: '3.0.0',
      info: { title: 'Test API', version: '1.0.0' },
      paths: {
        '/pets/{id}/documents': {
          post: {
            operationId: 'uploadPetDocuments',
            summary: 'Upload multiple documents for a pet',
            parameters: [
              {
                name: 'id',
                in: 'path',
                required: true,
                schema: { type: 'integer' },
              },
            ],
            requestBody: {
              required: true,
              content: {
                'multipart/form-data': {
                  schema: {
                    type: 'object',
                    required: ['documents'],
                    properties: {
                      documents: {
                        type: 'array',
                        items: {
                          type: 'string',
                          format: 'binary',
                        },
                        description: 'The documents to upload (max 5 files)',
                      },
                      tags: {
                        type: 'array',
                        items: {
                          type: 'string',
                        },
                        description: 'Optional tags for the documents',
                      },
                    },
                  },
                },
              },
            },
            responses: {
              '201': {
                description: 'Documents uploaded successfully',
              },
            },
          },
        },
      },
    }

    const converter = new OpenAPIToMCPConverter(spec)
    const { tools } = converter.convertToMCPTools()
    expect(Object.keys(tools)).toHaveLength(1)

    const [tool] = Object.values(tools)
    expect(tool.methods).toHaveLength(1)
    const [method] = tool.methods
    expect(method.name).toBe('uploadPetDocuments')
    expect(method.description).toContain('Upload multiple documents')

    // Check parameters
    expect(method.inputSchema.properties).toEqual({
      id: {
        type: 'integer',
      },
      documents: {
        type: 'array',
        items: {
          anyOf: [
            { type: 'string', format: 'uri-reference', description: 'absolute paths to local files' },
            { type: 'string' },
            { type: 'object', additionalProperties: true },
          ],
        },
        description: expect.stringContaining('max 5 files'),
      },
      tags: {
        type: 'array',
        items: {
          anyOf: [
            { type: 'string' },
            { type: 'string' },
            { type: 'object', additionalProperties: true },
          ],
        },
        description: expect.stringContaining('Optional tags'),
      },
    })

    expect(method.inputSchema.required).toContain('id')
    expect(method.inputSchema.required).toContain('documents')
    expect(method.inputSchema.required).not.toContain('tags')
  })

  it('handles complex multipart forms with mixed content', () => {
    const spec: OpenAPIV3.Document = {
      openapi: '3.0.0',
      info: { title: 'Test API', version: '1.0.0' },
      paths: {
        '/pets/{id}/profile': {
          post: {
            operationId: 'updatePetProfile',
            summary: 'Update pet profile with images and data',
            parameters: [
              {
                name: 'id',
                in: 'path',
                required: true,
                schema: { type: 'integer' },
              },
            ],
            requestBody: {
              required: true,
              content: {
                'multipart/form-data': {
                  schema: {
                    type: 'object',
                    required: ['avatar', 'details'],
                    properties: {
                      avatar: {
                        type: 'string',
                        format: 'binary',
                        description: 'Profile picture',
                      },
                      gallery: {
                        type: 'array',
                        items: {
                          type: 'string',
                          format: 'binary',
                        },
                        description: 'Additional pet photos',
                      },
                      details: {
                        type: 'object',
                        properties: {
                          name: { type: 'string' },
                          age: { type: 'integer' },
                          breed: { type: 'string' },
                        },
                      },
                      preferences: {
                        type: 'array',
                        items: {
                          type: 'object',
                          properties: {
                            category: { type: 'string' },
                            value: { type: 'string' },
                          },
                        },
                      },
                    },
                  },
                },
              },
            },
            responses: {
              '200': {
                description: 'Profile updated successfully',
              },
            },
          },
        },
      },
    }

    const converter = new OpenAPIToMCPConverter(spec)
    const { tools } = converter.convertToMCPTools()
    expect(Object.keys(tools)).toHaveLength(1)

    const [tool] = Object.values(tools)
    expect(tool.methods).toHaveLength(1)
    const [method] = tool.methods
    expect(method.name).toBe('updatePetProfile')
    expect(method.description).toContain('Update pet profile')

    // Check parameters
    expect(method.inputSchema.properties).toEqual({
      id: {
        type: 'integer',
      },
      avatar: {
        type: 'string',
        format: 'uri-reference',
        description: expect.stringContaining('Profile picture (absolute paths to local files)'),
      },
      gallery: {
        type: 'array',
        items: {
          anyOf: [
            { type: 'string', format: 'uri-reference', description: 'absolute paths to local files' },
            { type: 'string' },
            { type: 'object', additionalProperties: true },
          ],
        },
        description: expect.stringContaining('Additional pet photos'),
      },
      details: {
        anyOf: [
          {
            type: 'object',
            properties: {
              name: { type: 'string' },
              age: { type: 'integer' },
              breed: { type: 'string' },
            },
            additionalProperties: true,
          },
          { type: 'string' },
        ],
      },
      preferences: {
        type: 'array',
        items: {
          anyOf: [
            {
              type: 'object',
              properties: {
                category: { type: 'string' },
                value: { type: 'string' },
              },
              additionalProperties: true,
            },
            { type: 'string' },
            { type: 'object', additionalProperties: true },
          ],
        },
      },
    })

    expect(method.inputSchema.required).toContain('id')
    expect(method.inputSchema.required).toContain('avatar')
    expect(method.inputSchema.required).toContain('details')
    expect(method.inputSchema.required).not.toContain('gallery')
    expect(method.inputSchema.required).not.toContain('preferences')
  })

  it('handles optional file uploads in multipart forms', () => {
    const spec: OpenAPIV3.Document = {
      openapi: '3.0.0',
      info: { title: 'Test API', version: '1.0.0' },
      paths: {
        '/pets/{id}/metadata': {
          post: {
            operationId: 'updatePetMetadata',
            summary: 'Update pet metadata with optional attachments',
            parameters: [
              {
                name: 'id',
                in: 'path',
                required: true,
                schema: { type: 'integer' },
              },
            ],
            requestBody: {
              required: true,
              content: {
                'multipart/form-data': {
                  schema: {
                    type: 'object',
                    required: ['metadata'],
                    properties: {
                      metadata: {
                        type: 'object',
                        required: ['name'],
                        properties: {
                          name: { type: 'string' },
                          description: { type: 'string' },
                        },
                      },
                      certificate: {
                        type: 'string',
                        format: 'binary',
                        description: 'Optional pet certificate',
                      },
                      vaccinations: {
                        type: 'array',
                        items: {
                          type: 'string',
                          format: 'binary',
                        },
                        description: 'Optional vaccination records',
                      },
                    },
                  },
                },
              },
            },
            responses: {
              '200': {
                description: 'Metadata updated successfully',
              },
            },
          },
        },
      },
    }

    const converter = new OpenAPIToMCPConverter(spec)
    const { tools } = converter.convertToMCPTools()
    const [tool] = Object.values(tools)
    const [method] = tool.methods

    expect(method.name).toBe('updatePetMetadata')
    expect(method.inputSchema.required).toContain('id')
    expect(method.inputSchema.required).toContain('metadata')
    expect(method.inputSchema.required).not.toContain('certificate')
    expect(method.inputSchema.required).not.toContain('vaccinations')

    expect(method.inputSchema.properties).toEqual({
      id: {
        type: 'integer',
      },
      metadata: {
        anyOf: [
          {
            type: 'object',
            required: ['name'],
            properties: {
              name: { type: 'string' },
              description: { type: 'string' },
            },
            additionalProperties: true,
          },
          { type: 'string' },
        ],
      },
      certificate: {
        type: 'string',
        format: 'uri-reference',
        description: expect.stringContaining('Optional pet certificate (absolute paths to local files)'),
      },
      vaccinations: {
        type: 'array',
        items: {
          anyOf: [
            { type: 'string', format: 'uri-reference', description: 'absolute paths to local files' },
            { type: 'string' },
            { type: 'object', additionalProperties: true },
          ],
        },
        description: expect.stringContaining('Optional vaccination records'),
      },
    })
  })

  it('handles nested objects with file arrays in multipart forms', () => {
    const spec: OpenAPIV3.Document = {
      openapi: '3.0.0',
      info: { title: 'Test API', version: '1.0.0' },
      paths: {
        '/pets/{id}/medical-records': {
          post: {
            operationId: 'addMedicalRecord',
            summary: 'Add medical record with attachments',
            parameters: [
              {
                name: 'id',
                in: 'path',
                required: true,
                schema: { type: 'integer' },
              },
            ],
            requestBody: {
              required: true,
              content: {
                'multipart/form-data': {
                  schema: {
                    type: 'object',
                    required: ['record'],
                    properties: {
                      record: {
                        type: 'object',
                        required: ['date', 'type'],
                        properties: {
                          date: { type: 'string', format: 'date' },
                          type: { type: 'string' },
                          notes: { type: 'string' },
                          attachments: {
                            type: 'array',
                            items: {
                              type: 'object',
                              required: ['file', 'type'],
                              properties: {
                                file: {
                                  type: 'string',
                                  format: 'binary',
                                },
                                type: {
                                  type: 'string',
                                  enum: ['xray', 'lab', 'prescription'],
                                },
                                description: { type: 'string' },
                              },
                            },
                          },
                        },
                      },
                    },
                  },
                },
              },
            },
            responses: {
              '201': {
                description: 'Medical record added successfully',
              },
            },
          },
        },
      },
    }

    const converter = new OpenAPIToMCPConverter(spec)
    const { tools } = converter.convertToMCPTools()
    const [tool] = Object.values(tools)
    const [method] = tool.methods

    expect(method.name).toBe('addMedicalRecord')
    expect(method.inputSchema.required).toContain('id')
    expect(method.inputSchema.required).toContain('record')

    // Verify nested structure is preserved (record is wrapped in anyOf with string fallback)
    const recordSchemaWrapper = method.inputSchema.properties!.record as any
    expect(recordSchemaWrapper.anyOf).toHaveLength(2)
    const recordSchema = recordSchemaWrapper.anyOf[0]
    expect(recordSchema.type).toBe('object')
    expect(recordSchema.required).toContain('date')
    expect(recordSchema.required).toContain('type')

    // Verify nested file array structure
    const attachmentsSchema = recordSchema.properties.attachments
    expect(attachmentsSchema.type).toBe('array')
    expect(attachmentsSchema.items.type).toBe('object')
    expect(attachmentsSchema.items.properties.file.format).toBe('uri-reference')
    expect(attachmentsSchema.items.properties.file.description).toBe('absolute paths to local files')
    expect(attachmentsSchema.items.required).toContain('file')
    expect(attachmentsSchema.items.required).toContain('type')
  })

  it('handles oneOf/anyOf schemas with file uploads', () => {
    const spec: OpenAPIV3.Document = {
      openapi: '3.0.0',
      info: { title: 'Test API', version: '1.0.0' },
      paths: {
        '/pets/{id}/content': {
          post: {
            operationId: 'addPetContent',
            summary: 'Add pet content (photo or document)',
            parameters: [
              {
                name: 'id',
                in: 'path',
                required: true,
                schema: { type: 'integer' },
              },
            ],
            requestBody: {
              required: true,
              content: {
                'multipart/form-data': {
                  schema: {
                    type: 'object',
                    required: ['content'],
                    properties: {
                      content: {
                        oneOf: [
                          {
                            type: 'object',
                            required: ['photo', 'isProfile'],
                            properties: {
                              photo: {
                                type: 'string',
                                format: 'binary',
                              },
                              isProfile: {
                                type: 'boolean',
                              },
                            },
                          },
                          {
                            type: 'object',
                            required: ['document', 'category'],
                            properties: {
                              document: {
                                type: 'string',
                                format: 'binary',
                              },
                              category: {
                                type: 'string',
                                enum: ['medical', 'training', 'adoption'],
                              },
                            },
                          },
                        ],
                      },
                    },
                  },
                },
              },
            },
            responses: {
              '201': {
                description: 'Content added successfully',
              },
            },
          },
        },
      },
    }

    const converter = new OpenAPIToMCPConverter(spec)
    const { tools } = converter.convertToMCPTools()
    const [tool] = Object.values(tools)
    const [method] = tool.methods

    expect(method.name).toBe('addPetContent')
    expect(method.inputSchema.required).toContain('id')
    expect(method.inputSchema.required).toContain('content')

    // Verify oneOf structure is preserved (content is wrapped in anyOf with string fallback)
    const contentSchemaWrapper = method.inputSchema.properties!.content as any
    expect(contentSchemaWrapper.anyOf).toHaveLength(2)
    const contentSchema = contentSchemaWrapper.anyOf[0]
    expect(contentSchema.oneOf).toHaveLength(2)

    // Check photo option
    const photoOption = contentSchema.oneOf[0]
    expect(photoOption.type).toBe('object')
    expect(photoOption.properties.photo.format).toBe('uri-reference')
    expect(photoOption.properties.photo.description).toBe('absolute paths to local files')
    expect(photoOption.required).toContain('photo')
    expect(photoOption.required).toContain('isProfile')

    // Check document option
    const documentOption = contentSchema.oneOf[1]
    expect(documentOption.type).toBe('object')
    expect(documentOption.properties.document.format).toBe('uri-reference')
    expect(documentOption.properties.document.description).toBe('absolute paths to local files')
    expect(documentOption.required).toContain('document')
    expect(documentOption.required).toContain('category')
  })
})
