import { OpenAPIRegistry, OpenApiGeneratorV3, extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
import { z } from 'zod';
import { getBetterAuthBaseUrl } from '../utils/env';

extendZodWithOpenApi(z);

const registry = new OpenAPIRegistry();

const ErrorResponseSchema = registry.register(
  'ErrorResponse',
  z.object({
    message: z.string(),
    errors: z.unknown().optional(),
  }),
);

const VideoRecordSchema = registry.register(
  'VideoRecord',
  z.object({
    id: z.string().uuid(),
    objectKey: z.string(),
    bucket: z.string(),
    status: z.string(),
    createdAt: z.string().datetime(),
    expiresAt: z.string().datetime().nullable(),
  }),
);

const VideoUploadUrlRequestSchema = registry.register(
  'VideoUploadUrlRequest',
  z.object({
    fileName: z.string().min(1).max(255),
    deviceId: z.string().uuid(),
    prefix: z.string().optional(),
    recordingId: z.string().uuid().optional(),
    eventId: z.string().uuid().optional(),
  }),
);

const AdminUploadUrlRequestSchema = registry.register(
  'AdminUploadUrlRequest',
  z.object({
    fileName: z.string().min(1).max(255),
    prefix: z.string().optional(),
  }),
);

const UploadUrlResponseSchema = registry.register(
  'UploadUrlResponse',
  z.object({
    message: z.string(),
    bucket: z.string(),
    objectKey: z.string(),
    uploadUrl: z.string().url(),
    expiresInSeconds: z.number().int().positive(),
    video: VideoRecordSchema,
  }),
);

const DownloadUrlQuerySchema = registry.register(
  'DownloadUrlQuery',
  z.object({
    objectKey: z.string().min(1),
  }),
);

const DownloadUrlResponseSchema = registry.register(
  'DownloadUrlResponse',
  z.object({
    message: z.string(),
    bucket: z.string(),
    objectKey: z.string(),
    downloadUrl: z.string().url(),
    expiresInSeconds: z.number().int().positive(),
  }),
);

const ListVideosQuerySchema = registry.register(
  'ListVideosQuery',
  z.object({
    prefix: z.string().optional(),
    limit: z.coerce.number().int().min(1).max(100).default(20),
  }),
);

const ListedObjectSchema = registry.register(
  'ListedObject',
  z.object({
    objectKey: z.string().nullable(),
    size: z.number(),
    etag: z.string().nullable(),
    lastModified: z.string().datetime().nullable(),
  }),
);

const ListVideosResponseSchema = registry.register(
  'ListVideosResponse',
  z.object({
    bucket: z.string(),
    count: z.number().int().nonnegative(),
    objects: z.array(ListedObjectSchema),
  }),
);

const DeleteVideoResponseSchema = registry.register(
  'DeleteVideoResponse',
  z.object({
    message: z.string(),
    bucket: z.string(),
    objectKey: z.string(),
  }),
);

const DeviceSchema = registry.register(
  'Device',
  z.object({
    id: z.string().uuid(),
    userId: z.string().uuid(),
    name: z.string().nullable(),
    role: z.enum(['camera', 'client']),
    platform: z.string().nullable(),
    appVersion: z.string().nullable(),
    status: z.string(),
    isCamera: z.boolean(),
    lastSeenAt: z.string().datetime().nullable(),
    createdAt: z.string().datetime(),
    updatedAt: z.string().datetime(),
  }),
);

const DeviceLinkSchema = registry.register(
  'DeviceLink',
  z.object({
    id: z.string().uuid(),
    ownerUserId: z.string().uuid(),
    cameraDeviceId: z.string().uuid(),
    clientDeviceId: z.string().uuid(),
    status: z.string(),
    createdAt: z.string().datetime(),
    updatedAt: z.string().datetime(),
  }),
);

const DeviceCommandSchema = registry.register(
  'DeviceCommand',
  z.object({
    id: z.string().uuid(),
    ownerUserId: z.string().uuid(),
    sourceDeviceId: z.string().uuid(),
    targetDeviceId: z.string().uuid(),
    commandType: z.string(),
    payload: z.record(z.string(), z.unknown()).nullable(),
    status: z.string(),
    retryCount: z.number().int(),
    lastDispatchedAt: z.string().datetime().nullable(),
    acknowledgedAt: z.string().datetime().nullable(),
    error: z.string().nullable(),
    createdAt: z.string().datetime(),
    updatedAt: z.string().datetime(),
  }),
);

registry.registerPath({
  method: 'get',
  path: '/',
  summary: 'Health check',
  tags: ['System'],
  responses: {
    200: {
      description: 'Service health text',
      content: {
        'text/plain': {
          schema: z.string(),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/videos/upload-url',
  summary: 'Create a presigned upload URL and attach it to a recording',
  tags: ['Videos'],
  security: [{ cookieAuth: [] }],
  request: {
    body: {
      content: {
        'application/json': {
          schema: VideoUploadUrlRequestSchema,
        },
      },
    },
  },
  responses: {
    201: {
      description: 'Presigned upload URL generated',
      content: {
        'application/json': {
          schema: UploadUrlResponseSchema,
        },
      },
    },
    400: {
      description: 'Invalid request body',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
    401: {
      description: 'Unauthorized',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
  },
});

registry.registerPath({
  method: 'get',
  path: '/videos/download-url',
  summary: 'Create a signed download URL',
  tags: ['Videos'],
  security: [{ cookieAuth: [] }],
  request: {
    query: DownloadUrlQuerySchema,
  },
  responses: {
    200: {
      description: 'Signed download URL generated',
      content: {
        'application/json': {
          schema: DownloadUrlResponseSchema,
        },
      },
    },
    400: {
      description: 'Invalid query params',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
    401: {
      description: 'Unauthorized',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
  },
});

registry.registerPath({
  method: 'get',
  path: '/videos',
  summary: 'List objects in the configured bucket',
  tags: ['Videos'],
  security: [{ cookieAuth: [] }],
  request: {
    query: ListVideosQuerySchema,
  },
  responses: {
    200: {
      description: 'Object listing',
      content: {
        'application/json': {
          schema: ListVideosResponseSchema,
        },
      },
    },
    400: {
      description: 'Invalid query params',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
    401: {
      description: 'Unauthorized',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
  },
});

registry.registerPath({
  method: 'delete',
  path: '/videos',
  summary: 'Delete object by object key',
  tags: ['Videos'],
  security: [{ cookieAuth: [] }],
  request: {
    query: DownloadUrlQuerySchema,
  },
  responses: {
    200: {
      description: 'Object deleted',
      content: {
        'application/json': {
          schema: DeleteVideoResponseSchema,
        },
      },
    },
    400: {
      description: 'Invalid query params',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
    401: {
      description: 'Unauthorized',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/admin/upload-url',
  summary: 'Admin: create presigned upload URL',
  tags: ['Admin'],
  security: [{ basicAuth: [] }],
  request: {
    body: {
      content: {
        'application/json': {
          schema: AdminUploadUrlRequestSchema,
        },
      },
    },
  },
  responses: {
    201: {
      description: 'Admin upload URL generated',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
            bucket: z.string(),
            objectKey: z.string(),
            uploadUrl: z.string().url(),
            expiresAt: z.string().datetime(),
            expiresInSeconds: z.number().int().positive(),
          }),
        },
      },
    },
    400: {
      description: 'Invalid request body',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
    401: {
      description: 'Unauthorized',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
  },
});

registry.registerPath({
  method: 'get',
  path: '/admin/objects',
  summary: 'Admin: list objects in bucket',
  tags: ['Admin'],
  security: [{ basicAuth: [] }],
  request: {
    query: ListVideosQuerySchema,
  },
  responses: {
    200: {
      description: 'Admin object listing',
      content: {
        'application/json': {
          schema: ListVideosResponseSchema,
        },
      },
    },
    400: {
      description: 'Invalid query params',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
    401: {
      description: 'Unauthorized',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
  },
});

registry.registerPath({
  method: 'delete',
  path: '/admin/object',
  summary: 'Admin: delete object by key',
  tags: ['Admin'],
  security: [{ basicAuth: [] }],
  request: {
    query: DownloadUrlQuerySchema,
  },
  responses: {
    200: {
      description: 'Object deleted',
      content: {
        'application/json': {
          schema: DeleteVideoResponseSchema,
        },
      },
    },
    400: {
      description: 'Invalid query params',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
    401: {
      description: 'Unauthorized',
      content: {
        'application/json': {
          schema: ErrorResponseSchema,
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/devices/register',
  summary: 'Register a mobile device and issue a device token',
  tags: ['Devices'],
  security: [{ cookieAuth: [] }],
  request: {
    body: {
      content: {
        'application/json': {
          schema: z.object({
            name: z.string().optional(),
            role: z.enum(['camera', 'client']),
            platform: z.string().optional(),
            appVersion: z.string().optional(),
            pushToken: z.string().optional(),
          }),
        },
      },
    },
  },
  responses: {
    201: {
      description: 'Device registered',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
            device: DeviceSchema,
            deviceToken: z.string(),
          }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'get',
  path: '/devices',
  summary: 'List devices for the authenticated user',
  tags: ['Devices'],
  security: [{ cookieAuth: [] }],
  responses: {
    200: {
      description: 'User devices',
      content: {
        'application/json': {
          schema: z.object({
            count: z.number().int(),
            devices: z.array(DeviceSchema),
          }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/devices/{deviceId}/heartbeat',
  summary: 'Record heartbeat for a device using bearer device token',
  tags: ['Devices'],
  security: [{ bearerDeviceToken: [] }],
  request: {
    params: z.object({ deviceId: z.string().uuid() }),
    body: {
      content: {
        'application/json': {
          schema: z.object({ status: z.enum(['online', 'offline']).optional() }),
        },
      },
    },
  },
  responses: {
    200: {
      description: 'Heartbeat recorded',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
            device: z.object({
              id: z.string().uuid(),
              status: z.string(),
              lastSeenAt: z.string().datetime().nullable(),
              updatedAt: z.string().datetime(),
            }),
          }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/device-links',
  summary: 'Link a client device to a camera device',
  tags: ['Device Links'],
  security: [{ cookieAuth: [] }],
  request: {
    body: {
      content: {
        'application/json': {
          schema: z.object({
            cameraDeviceId: z.string().uuid(),
            clientDeviceId: z.string().uuid(),
          }),
        },
      },
    },
  },
  responses: {
    201: {
      description: 'Link created',
      content: {
        'application/json': {
          schema: z.object({ message: z.string(), link: DeviceLinkSchema }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/commands',
  summary: 'Create and dispatch a realtime command from client to camera',
  tags: ['Commands'],
  security: [{ cookieAuth: [] }],
  request: {
    body: {
      content: {
        'application/json': {
          schema: z.object({
            sourceDeviceId: z.string().uuid(),
            targetDeviceId: z.string().uuid(),
            commandType: z.enum(['start_stream', 'stop_stream', 'ping', 'update_settings']),
            payload: z.record(z.string(), z.unknown()).optional(),
          }),
        },
      },
    },
  },
  responses: {
    201: {
      description: 'Command queued',
      content: {
        'application/json': {
          schema: z.object({ message: z.string(), command: DeviceCommandSchema }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/events/motion/start',
  summary: 'Camera device starts a motion event and notifies linked clients',
  tags: ['Events'],
  security: [{ bearerDeviceToken: [] }],
  request: {
    body: {
      content: {
        'application/json': {
          schema: z.object({
            title: z.string().optional(),
            triggeredBy: z.string().optional(),
            videoUrl: z.string().url().optional(),
          }),
        },
      },
    },
  },
  responses: {
    201: {
      description: 'Motion event started',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
            event: z.object({
              id: z.string().uuid(),
              userId: z.string().uuid(),
              deviceId: z.string().uuid().nullable(),
              title: z.string().nullable(),
              triggeredBy: z.string().nullable(),
              status: z.string(),
              startedAt: z.string().datetime(),
              endedAt: z.string().datetime().nullable(),
              videoUrl: z.string().nullable(),
              createdAt: z.string().datetime(),
              updatedAt: z.string().datetime(),
              notifiedAt: z.string().datetime().nullable(),
            }),
            notifiedClients: z.number().int(),
          }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/events/{eventId}/motion/end',
  summary: 'Camera device ends a motion event and notifies linked clients',
  tags: ['Events'],
  security: [{ bearerDeviceToken: [] }],
  request: {
    params: z.object({ eventId: z.string().uuid() }),
    body: {
      content: {
        'application/json': {
          schema: z.object({
            status: z.enum(['completed', 'cancelled', 'failed']).optional(),
            videoUrl: z.string().url().optional(),
          }),
        },
      },
    },
  },
  responses: {
    200: {
      description: 'Motion event ended',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
            event: z.object({
              id: z.string().uuid(),
              status: z.string(),
              endedAt: z.string().datetime().nullable(),
              updatedAt: z.string().datetime(),
            }),
            notifiedClients: z.number().int(),
          }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'get',
  path: '/events',
  summary: 'List events for authenticated user',
  tags: ['Events'],
  security: [{ cookieAuth: [] }],
  request: {
    query: z.object({
      status: z.string().optional(),
      limit: z.coerce.number().int().min(1).max(100).default(25),
    }),
  },
  responses: {
    200: {
      description: 'User event list',
      content: {
        'application/json': {
          schema: z.object({
            count: z.number().int(),
            events: z.array(
              z.object({
                id: z.string().uuid(),
                status: z.string(),
                startedAt: z.string().datetime(),
                endedAt: z.string().datetime().nullable(),
                deviceId: z.string().uuid().nullable(),
                title: z.string().nullable(),
                triggeredBy: z.string().nullable(),
                videoUrl: z.string().nullable(),
              }),
            ),
          }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/streams/request',
  summary: 'Client device requests an on-demand stream from a linked camera',
  tags: ['Streams'],
  security: [{ bearerDeviceToken: [] }],
  request: {
    body: {
      content: {
        'application/json': {
          schema: z.object({
            cameraDeviceId: z.string().uuid(),
            reason: z.enum(['on_demand', 'motion_follow_up']).optional(),
            metadata: z.record(z.string(), z.unknown()).optional(),
          }),
        },
      },
    },
  },
  responses: {
    201: {
      description: 'Stream request created',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
            streamSession: z.object({
              id: z.string().uuid(),
              ownerUserId: z.string().uuid(),
              cameraDeviceId: z.string().uuid(),
              requesterDeviceId: z.string().uuid(),
              status: z.string(),
              reason: z.string(),
              metadata: z.record(z.string(), z.unknown()).nullable().optional(),
              startedAt: z.string().datetime().nullable().optional(),
              endedAt: z.string().datetime().nullable().optional(),
              createdAt: z.string().datetime().optional(),
              updatedAt: z.string().datetime().optional(),
            }),
          }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/streams/{streamSessionId}/accept',
  summary: 'Camera device accepts a pending on-demand stream request',
  tags: ['Streams'],
  security: [{ bearerDeviceToken: [] }],
  request: {
    params: z.object({ streamSessionId: z.string().uuid() }),
    body: {
      content: {
        'application/json': {
          schema: z.object({
            streamKey: z.string().optional(),
            metadata: z.record(z.string(), z.unknown()).optional(),
          }),
        },
      },
    },
  },
  responses: {
    200: {
      description: 'Stream session accepted',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
            streamSession: z.object({
              id: z.string().uuid(),
              ownerUserId: z.string().uuid(),
              cameraDeviceId: z.string().uuid(),
              requesterDeviceId: z.string().uuid(),
              status: z.string(),
              reason: z.string(),
              metadata: z.record(z.string(), z.unknown()).nullable().optional(),
              startedAt: z.string().datetime().nullable(),
              endedAt: z.string().datetime().nullable().optional(),
              createdAt: z.string().datetime().optional(),
              updatedAt: z.string().datetime().optional(),
            }),
          }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'post',
  path: '/streams/{streamSessionId}/end',
  summary: 'Requester or camera ends an active stream session',
  tags: ['Streams'],
  security: [{ bearerDeviceToken: [] }],
  request: {
    params: z.object({ streamSessionId: z.string().uuid() }),
    body: {
      content: {
        'application/json': {
          schema: z.object({
            reason: z.enum(['completed', 'cancelled', 'failed']).optional(),
          }),
        },
      },
    },
  },
  responses: {
    200: {
      description: 'Stream session ended',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
            streamSession: z.object({
              id: z.string().uuid(),
              status: z.string(),
              endedAt: z.string().datetime().nullable(),
            }),
          }),
        },
      },
    },
  },
});

registry.registerPath({
  method: 'get',
  path: '/streams/me/list',
  summary: 'List stream sessions for current device',
  tags: ['Streams'],
  security: [{ bearerDeviceToken: [] }],
  request: {
    query: z.object({
      status: z.string().optional(),
      limit: z.coerce.number().int().min(1).max(100).default(25),
    }),
  },
  responses: {
    200: {
      description: 'Stream sessions list',
      content: {
        'application/json': {
          schema: z.object({
            count: z.number().int(),
            streamSessions: z.array(
              z.object({
                id: z.string().uuid(),
                cameraDeviceId: z.string().uuid(),
                requesterDeviceId: z.string().uuid(),
                status: z.string(),
                reason: z.string(),
                streamKey: z.string().nullable(),
                mediaProvider: z.string(),
                mediaSessionId: z.string().nullable(),
              }),
            ),
          }),
        },
      },
    },
  },
});

export function buildOpenApiDocument() {
  const generator = new OpenApiGeneratorV3(registry.definitions);
  const document = generator.generateDocument({
    openapi: '3.0.3',
    info: {
      title: 'Backend API',
      version: '1.0.0',
      description: 'Auto-generated API documentation from Zod schemas.',
    },
    servers: [{ url: getBetterAuthBaseUrl() }],
    tags: [
      { name: 'System', description: 'Service endpoints' },
      { name: 'Videos', description: 'Authenticated video object operations' },
      { name: 'Admin', description: 'Basic-auth protected admin operations' },
      { name: 'Devices', description: 'Device registration and heartbeat endpoints' },
      { name: 'Device Links', description: 'Client-camera authorization links' },
      { name: 'Commands', description: 'Realtime command dispatch and status' },
      { name: 'Events', description: 'Motion event lifecycle and user event history' },
      { name: 'Streams', description: 'On-demand live stream control lifecycle' },
    ],
  });

  document.components = {
    ...(document.components ?? {}),
    securitySchemes: {
      cookieAuth: {
        type: 'apiKey',
        in: 'cookie',
        name: 'better-auth.session_token',
        description: 'Better Auth session cookie',
      },
      basicAuth: {
        type: 'http',
        scheme: 'basic',
      },
      bearerDeviceToken: {
        type: 'http',
        scheme: 'bearer',
      },
    },
  };

  return document;
}
