/**
 * API Response Schemas
 * FE-TYPE-002: Create Zod schemas to validate API responses at runtime
 *
 * Provides Zod schemas for all API response types to ensure type safety
 * and runtime validation of API responses.
 *
 * Schema Versioning:
 * - All schemas are versioned to track changes over time
 * - Version matches OpenAPI spec version when schema is generated from spec
 * - Manual schemas use version "1.0.0" as baseline
 * - Schema version helps identify breaking changes and migration needs
 */

import { z } from 'zod';
import { normalizeObjectIds } from '@/utils/idNormalization';

/**
 * Schema version metadata
 * Action 1.2.2.6: Add schema versioning to track schema evolution
 */
export const SCHEMA_VERSION = '1.2.0'; // Matches OpenAPI spec version
export const SCHEMA_VERSION_DATE = '2025-01-27'; // Last schema update date

/**
 * Helper to create versioned schema with metadata
 * This allows tracking schema versions for migration and compatibility checks
 */
export function createVersionedSchema<T extends z.ZodTypeAny>(
  schema: T,
  version: string = SCHEMA_VERSION,
  description?: string,
): T & { _version?: string; _description?: string } {
  // Attach metadata to schema (non-enumerable to avoid serialization issues)
  Object.defineProperty(schema, '_version', {
    value: version,
    enumerable: false,
    writable: false,
  });
  if (description) {
    Object.defineProperty(schema, '_description', {
      value: description,
      enumerable: false,
      writable: false,
    });
  }
  return schema as T & { _version?: string; _description?: string };
}

/**
 * Base UUID schema
 */
export const uuidSchema = z.string().uuid('Invalid UUID format');

/**
 * ISO8601 date string schema
 */
export const isoDateSchema = z
  .string()
  .datetime({ message: 'Invalid ISO8601 date format' });

/**
 * User schema
 * INT-TYPE-001: User.id is UUID (string) from backend - always string
 * Schema Version: 1.2.0
 */
export const userSchema = createVersionedSchema(
  z.object({
    id: uuidSchema, // UUID from backend - always string
    username: z.string().min(1),
    slug: z.string().optional(),
    email: z.string().email(),
    first_name: z.string().optional().nullable(),
    last_name: z.string().optional().nullable(),
    avatar: z.string().optional().nullable(),
    bio: z.string().optional().nullable(),
    location: z.string().optional().nullable(),
    birthdate: isoDateSchema.optional().nullable(),
    gender: z.string().optional().nullable(),
    username_changed_at: isoDateSchema.optional().nullable(),
    role: z.enum(['user', 'admin', 'super_admin']),
    is_active: z.boolean(),
    is_verified: z.boolean(),
    is_banned: z.boolean().optional(),
    is_admin: z.boolean(),
    is_public: z.boolean(),
    last_login_at: isoDateSchema.optional().nullable(),
    created_at: isoDateSchema,
    updated_at: isoDateSchema,
    is_2fa_enabled: z.boolean().optional(),
    social_links: z.record(z.any()).optional().nullable(),
  }),
  SCHEMA_VERSION,
  'User response schema - matches backend User model',
);

export type User = z.infer<typeof userSchema>;

/**
 * Message schema
 */
export const messageSchema = z.object({
  id: uuidSchema,
  conversation_id: uuidSchema,
  sender_id: uuidSchema,
  content: z.string(),
  message_type: z.enum(['text', 'image', 'audio', 'file']),
  attachment_url: z.string().url().optional(),
  created_at: isoDateSchema,
  updated_at: isoDateSchema,
  sender: userSchema.optional(),
});

export type Message = z.infer<typeof messageSchema>;

/**
 * Conversation schema
 */
export const conversationSchema = z.object({
  id: uuidSchema,
  name: z.string(),
  type: z.enum(['direct', 'group']),
  creator_id: uuidSchema,
  created_at: isoDateSchema,
  updated_at: isoDateSchema,
  participants: z.array(userSchema).optional(),
  last_message: messageSchema.optional(),
  unread_count: z.number().int().nonnegative().optional(),
});

export type Conversation = z.infer<typeof conversationSchema>;

/**
 * Track schema
 * INT-TYPE-002: Track.id is UUID (string) from backend - always string
 * Schema Version: 1.2.0
 */
export const trackSchema = createVersionedSchema(
  z.object({
    id: uuidSchema, // UUID from backend - always string
    creator_id: uuidSchema, // UUID from backend - always string
    file_id: uuidSchema.optional().nullable(),
    title: z.string().min(1),
    artist: z.string().min(1),
    album: z.string(),
    duration: z.number().int().nonnegative(),
    genre: z.string(),
    year: z.number().int().min(1900).max(2100),
    file_path: z.string(),
    file_size: z.number().nonnegative(),
    format: z.string(),
    bitrate: z.number().nonnegative(),
    sample_rate: z.number().nonnegative(),
    waveform_path: z.string().optional().nullable(),
    cover_art_path: z.string().optional().nullable(),
    is_public: z.boolean(),
    // INT-TYPE-004: TrackStatus enum aligned with backend
    status: z.enum(['uploading', 'processing', 'completed', 'failed']), // Matches TrackStatus enum
    status_message: z.string().optional().nullable(),
    stream_status: z.enum(['pending', 'processing', 'ready', 'error']),
    stream_manifest_url: z.string().url().optional().nullable(),
    play_count: z.number().int().nonnegative(),
    like_count: z.number().int().nonnegative(),
    created_at: isoDateSchema,
    updated_at: isoDateSchema,
    user: userSchema.optional(),
    is_premium: z.boolean().optional(),
    lyrics: z
      .array(z.object({ time: z.number(), text: z.string() }))
      .optional(),
    waveform_data: z.array(z.number()).optional(),
    tags: z.array(z.string()).optional(),
  }),
  SCHEMA_VERSION,
  'Track response schema - matches backend Track model',
);

export type Track = z.infer<typeof trackSchema>;

/**
 * Playlist schema
 * INT-TYPE-003: Playlist.id is UUID (string) from backend - always string
 * INT-TYPE-005: PlaylistVisibility enum support (backend uses is_public bool for now)
 * Schema Version: 1.2.0
 */
export const playlistSchema = createVersionedSchema(
  z.object({
    id: uuidSchema, // UUID from backend - always string
    user_id: uuidSchema, // UUID from backend - always string
    title: z.string().min(1),
    description: z.string().optional().nullable(),
    // INT-TYPE-005: Visibility enum (public, private, unlisted) - maps to is_public for now
    visibility: z.enum(['public', 'private', 'unlisted']).optional(),
    is_public: z.boolean(), // Backend field - maps to visibility: public=true, private=false
    cover_url: z.string().url().optional().nullable(),
    track_count: z.number().int().nonnegative(),
    follower_count: z.number().int().nonnegative(),
    created_at: isoDateSchema,
    updated_at: isoDateSchema,
    tracks: z.array(trackSchema).optional(),
    user: userSchema.optional(),
    playlist_tracks: z.array(z.any()).optional(), // Complex nested type
    collaborators: z.array(z.any()).optional(), // Complex nested type
  }),
  SCHEMA_VERSION,
  'Playlist response schema - matches backend Playlist model',
);

export type Playlist = z.infer<typeof playlistSchema>;

/**
 * Session schema
 */
export const sessionSchema = z.object({
  id: uuidSchema,
  user_id: uuidSchema,
  ip_address: z.string(),
  user_agent: z.string(),
  revoked_at: isoDateSchema.optional().nullable(),
  expires_at: isoDateSchema,
  created_at: isoDateSchema,
});

export type Session = z.infer<typeof sessionSchema>;

/**
 * AuditLog schema
 */
export const auditLogSchema = z.object({
  id: uuidSchema,
  user_id: uuidSchema.optional().nullable(),
  action: z.string(),
  resource: z.string(),
  resource_id: uuidSchema.optional().nullable(),
  metadata: z.record(z.any()).optional().nullable(),
  ip_address: z.string().optional().nullable(),
  user_agent: z.string().optional().nullable(),
  timestamp: isoDateSchema,
});

export type AuditLog = z.infer<typeof auditLogSchema>;

/**
 * ApiError schema
 * INT-TYPE-006: Aligned with backend error format (handlers/error_response.go)
 * Backend ErrorDetail: { field, message }
 * Schema Version: 1.2.0
 */
export const apiErrorSchema = createVersionedSchema(
  z.object({
    code: z.number().int(), // ErrorCode (1000-9999) or HTTP status code
    message: z.string(),
    // INT-TYPE-006: details matches backend ErrorDetail[] structure
    details: z
      .array(
        z.object({
          field: z.string(), // Field name from ErrorDetail.Field
          message: z.string(), // Error message from ErrorDetail.Message
          value: z.string().optional(), // Optional value that failed validation
        }),
      )
      .optional(),
    request_id: z.string().optional(), // Request ID for tracking
    timestamp: isoDateSchema, // ISO8601 timestamp
    context: z.record(z.any()).optional(), // Additional context (user_id, etc.)
    retry_after: z.number().int().positive().optional(), // Seconds before retry (for 429)
  }),
  SCHEMA_VERSION,
  'API Error response schema - matches backend error format',
);

export type ApiError = z.infer<typeof apiErrorSchema>;

/**
 * ApiResponse schema (generic)
 * Schema Version: 1.2.0
 */
export const apiResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
  createVersionedSchema(
    z.object({
      success: z.boolean(),
      data: dataSchema.nullable(),
      error: apiErrorSchema.optional(),
      message: z.string().optional(),
    }),
    SCHEMA_VERSION,
    'API Response wrapper schema',
  );

/**
 * PaginationData schema
 * Schema Version: 1.2.0
 */
export const paginationDataSchema = createVersionedSchema(
  z.object({
    page: z.number().int().positive(),
    limit: z.number().int().positive(),
    total: z.number().int().nonnegative(),
    total_pages: z.number().int().nonnegative(),
    has_next: z.boolean(),
    has_prev: z.boolean(),
    next_cursor: z.string().optional(),
    prev_cursor: z.string().optional(),
  }),
  SCHEMA_VERSION,
  'Pagination metadata schema',
);

export type PaginationData = z.infer<typeof paginationDataSchema>;

/**
 * ListResponse schema (generic)
 */
export const listResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
  z.object({
    items: z.array(itemSchema),
    pagination: paginationDataSchema,
  });

/**
 * Notification schema
 */
export const notificationSchema = z.object({
  id: uuidSchema,
  user_id: uuidSchema,
  type: z.enum(['new_message', 'track_uploaded', 'user_mentioned', 'system']),
  content: z.string(),
  read: z.boolean(),
  created_at: isoDateSchema,
});

export type Notification = z.infer<typeof notificationSchema>;

/**
 * PlaylistTrack schema
 * INT-TYPE-003: All IDs are UUID (string) from backend - always string
 */
export const playlistTrackSchema = z.object({
  id: uuidSchema, // UUID from backend - always string
  playlist_id: uuidSchema, // UUID from backend - always string
  track_id: uuidSchema, // UUID from backend - always string
  position: z.number().int().nonnegative(),
  added_by: uuidSchema,
  added_at: isoDateSchema,
  track: trackSchema.optional(),
});

export type PlaylistTrack = z.infer<typeof playlistTrackSchema>;

/**
 * PlaylistCollaborator schema
 * INT-TYPE-003: All IDs are UUID (string) from backend - always string
 * Schema Version: 1.2.0
 */
export const playlistCollaboratorSchema = z.object({
  id: uuidSchema, // UUID from backend - always string
  playlist_id: uuidSchema, // UUID from backend - always string
  user_id: uuidSchema, // UUID from backend - always string
  role: z.enum(['owner', 'editor', 'viewer']),
  created_at: isoDateSchema,
  user: userSchema.optional(),
});

export type PlaylistCollaborator = z.infer<typeof playlistCollaboratorSchema>;

/**
 * Validate and normalize API response
 *
 * @param schema - Zod schema to validate against
 * @param data - Data to validate
 * @param options - Validation options
 * @returns Validated and normalized data
 * @throws ZodError if validation fails
 */
export function validateApiResponse<T>(
  schema: z.ZodSchema<T>,
  data: unknown,
  options: {
    normalizeIds?: boolean;
  } = {},
): T {
  const { normalizeIds = true } = options;

  // Normalize IDs if requested
  let normalizedData = data;
  if (normalizeIds && typeof data === 'object' && data !== null) {
    normalizedData = normalizeObjectIds(data as Record<string, unknown>);
  }

  // Validate with schema
  // Note: Zod parse() is permissive by default (allows extra fields)
  // Strict mode would require using .strict() on object schemas, but we keep it simple
  return schema.parse(normalizedData);
}

/**
 * Safe validate API response (returns result instead of throwing)
 *
 * @param schema - Zod schema to validate against
 * @param data - Data to validate
 * @param options - Validation options
 * @returns Validation result
 */
export function safeValidateApiResponse<T>(
  schema: z.ZodSchema<T>,
  data: unknown,
  options: {
    normalizeIds?: boolean;
  } = {},
): {
  success: boolean;
  data?: T;
  error?: z.ZodError;
} {
  try {
    const validated = validateApiResponse(schema, data, options);
    return { success: true, data: validated };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return { success: false, error };
    }
    throw error;
  }
}

/**
 * Validate API response array
 */
export function validateApiResponseArray<T>(
  itemSchema: z.ZodSchema<T>,
  data: unknown,
  options?: {
    normalizeIds?: boolean;
  },
): T[] {
  const arraySchema = z.array(itemSchema);
  const { normalizeIds = true } = options || {};

  // Normalize IDs if requested
  let normalizedData = data;
  if (normalizeIds && Array.isArray(data)) {
    normalizedData = data.map((item) =>
      typeof item === 'object' && item !== null
        ? normalizeObjectIds(item as Record<string, unknown>)
        : item,
    );
  }

  // Validate with schema
  return arraySchema.parse(normalizedData);
}

/**
 * Validate paginated API response
 */
export function validatePaginatedResponse<T>(
  itemSchema: z.ZodSchema<T>,
  data: unknown,
  options?: {
    normalizeIds?: boolean;
  },
): {
  items: T[];
  pagination: PaginationData;
} {
  const paginatedSchema = listResponseSchema(itemSchema);
  return validateApiResponse(paginatedSchema, data, options);
}
