/**
 * Marketplace Catalog Service
 * Manages products, publishers, versions, and pricing
 */

import { getDb } from '../db/index.js'
import { logger } from '../lib/logger.js'
import { Context } from '../types/context.js'

export interface Publisher {
  id: string
  name: string
  displayName: string
  description?: string
  websiteUrl?: string
  logoUrl?: string
  verified: boolean
  metadata: Record<string, any>
  createdAt: Date
  updatedAt: Date
}

export interface Product {
  id: string
  name: string
  slug: string
  category: ProductCategory
  description?: string
  shortDescription?: string
  publisherId: string
  publisher?: Publisher
  status: ProductStatus
  featured: boolean
  iconUrl?: string
  documentationUrl?: string
  supportUrl?: string
  metadata: Record<string, any>
  tags: string[]
  createdAt: Date
  updatedAt: Date
  versions?: ProductVersion[]
  pricing?: PricingModel
  averageRating?: number
  reviewCount?: number
}

export enum ProductCategory {
  COMPUTE = 'COMPUTE',
  NETWORK_INFRA = 'NETWORK_INFRA',
  BLOCKCHAIN_STACK = 'BLOCKCHAIN_STACK',
  BLOCKCHAIN_TOOLS = 'BLOCKCHAIN_TOOLS',
  FINANCIAL_MESSAGING = 'FINANCIAL_MESSAGING',
  INTERNET_REGISTRY = 'INTERNET_REGISTRY',
  AI_LLM_AGENT = 'AI_LLM_AGENT',
}

export enum ProductStatus {
  DRAFT = 'DRAFT',
  PUBLISHED = 'PUBLISHED',
  ARCHIVED = 'ARCHIVED',
  DEPRECATED = 'DEPRECATED',
}

export interface ProductVersion {
  id: string
  productId: string
  version: string
  changelog?: string
  templateId?: string
  status: ProductVersionStatus
  isLatest: boolean
  releasedAt?: Date
  metadata: Record<string, any>
  createdAt: Date
  updatedAt: Date
}

export enum ProductVersionStatus {
  DRAFT = 'DRAFT',
  PUBLISHED = 'PUBLISHED',
  DEPRECATED = 'DEPRECATED',
}

export interface PricingModel {
  id: string
  productId: string
  productVersionId?: string
  pricingType: PricingType
  basePrice?: number
  currency: string
  billingPeriod?: BillingPeriod
  usageRates?: Record<string, any>
  metadata: Record<string, any>
  createdAt: Date
  updatedAt: Date
}

export enum PricingType {
  FREE = 'FREE',
  ONE_TIME = 'ONE_TIME',
  SUBSCRIPTION = 'SUBSCRIPTION',
  USAGE_BASED = 'USAGE_BASED',
  HYBRID = 'HYBRID',
}

export enum BillingPeriod {
  HOURLY = 'HOURLY',
  DAILY = 'DAILY',
  MONTHLY = 'MONTHLY',
  YEARLY = 'YEARLY',
}

export interface ProductReview {
  id: string
  productId: string
  userId: string
  rating: number
  title?: string
  reviewText?: string
  verifiedPurchase: boolean
  helpfulCount: number
  createdAt: Date
  updatedAt: Date
}

export interface ProductFilter {
  category?: ProductCategory
  status?: ProductStatus
  publisherId?: string
  tags?: string[]
  featured?: boolean
  search?: string
  limit?: number
  offset?: number
}

class CatalogService {
  /**
   * Get all publishers
   */
  async getPublishers(context: Context): Promise<Publisher[]> {
    const db = getDb()
    const result = await db.query(
      `SELECT * FROM publishers ORDER BY display_name ASC`
    )
    return result.rows.map(this.mapPublisher)
  }

  /**
   * Get publisher by ID
   */
  async getPublisher(context: Context, id: string): Promise<Publisher | null> {
    const db = getDb()
    const result = await db.query(`SELECT * FROM publishers WHERE id = $1`, [id])
    if (result.rows.length === 0) return null
    return this.mapPublisher(result.rows[0])
  }

  /**
   * Create publisher
   */
  async createPublisher(
    context: Context,
    input: {
      name: string
      displayName: string
      description?: string
      websiteUrl?: string
      logoUrl?: string
      metadata?: Record<string, any>
    }
  ): Promise<Publisher> {
    const db = getDb()
    const result = await db.query(
      `INSERT INTO publishers (name, display_name, description, website_url, logo_url, metadata)
       VALUES ($1, $2, $3, $4, $5, $6)
       RETURNING *`,
      [
        input.name,
        input.displayName,
        input.description || null,
        input.websiteUrl || null,
        input.logoUrl || null,
        JSON.stringify(input.metadata || {}),
      ]
    )
    logger.info('Publisher created', { publisherId: result.rows[0].id })
    return this.mapPublisher(result.rows[0])
  }

  /**
   * Get products with filtering
   */
  async getProducts(context: Context, filter?: ProductFilter): Promise<Product[]> {
    const db = getDb()
    const conditions: string[] = []
    const params: any[] = []
    let paramIndex = 1

    // Only show published products to non-admins
    if (context.user?.role !== 'ADMIN') {
      conditions.push(`p.status = 'PUBLISHED'`)
    } else if (filter?.status) {
      conditions.push(`p.status = $${paramIndex}`)
      params.push(filter.status)
      paramIndex++
    }

    if (filter?.category) {
      conditions.push(`p.category = $${paramIndex}`)
      params.push(filter.category)
      paramIndex++
    }

    if (filter?.publisherId) {
      conditions.push(`p.publisher_id = $${paramIndex}`)
      params.push(filter.publisherId)
      paramIndex++
    }

    if (filter?.featured !== undefined) {
      conditions.push(`p.featured = $${paramIndex}`)
      params.push(filter.featured)
      paramIndex++
    }

    if (filter?.tags && filter.tags.length > 0) {
      conditions.push(`p.tags && $${paramIndex}`)
      params.push(filter.tags)
      paramIndex++
    }

    if (filter?.search) {
      conditions.push(
        `to_tsvector('english', coalesce(p.name, '') || ' ' || coalesce(p.description, '') || ' ' || coalesce(p.short_description, '')) @@ plainto_tsquery('english', $${paramIndex})`
      )
      params.push(filter.search)
      paramIndex++
    }

    const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
    const limit = filter?.limit || 50
    const offset = filter?.offset || 0

    params.push(limit, offset)
    const limitClause = `LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`

    const query = `
      SELECT 
        p.*,
        pub.id as publisher_id,
        pub.name as publisher_name,
        pub.display_name as publisher_display_name,
        pub.verified as publisher_verified,
        pub.logo_url as publisher_logo_url,
        COALESCE(AVG(pr.rating), 0) as average_rating,
        COUNT(DISTINCT pr.id) as review_count
      FROM products p
      LEFT JOIN publishers pub ON p.publisher_id = pub.id
      LEFT JOIN product_reviews pr ON p.id = pr.product_id
      ${whereClause}
      GROUP BY p.id, pub.id
      ORDER BY p.featured DESC, p.created_at DESC
      ${limitClause}
    `

    const result = await db.query(query, params)
    return result.rows.map((row) => this.mapProduct(row))
  }

  /**
   * Get product by ID
   */
  async getProduct(context: Context, id: string): Promise<Product | null> {
    const db = getDb()
    const result = await db.query(
      `SELECT 
        p.*,
        pub.id as publisher_id,
        pub.name as publisher_name,
        pub.display_name as publisher_display_name,
        pub.verified as publisher_verified,
        pub.logo_url as publisher_logo_url,
        COALESCE(AVG(pr.rating), 0) as average_rating,
        COUNT(DISTINCT pr.id) as review_count
      FROM products p
      LEFT JOIN publishers pub ON p.publisher_id = pub.id
      LEFT JOIN product_reviews pr ON p.id = pr.product_id
      WHERE p.id = $1
      GROUP BY p.id, pub.id`,
      [id]
    )

    if (result.rows.length === 0) return null

    const product = this.mapProduct(result.rows[0])

    // Load versions
    const versionsResult = await db.query(
      `SELECT * FROM product_versions WHERE product_id = $1 ORDER BY created_at DESC`,
      [id]
    )
    product.versions = versionsResult.rows.map(this.mapProductVersion)

    // Load pricing
    const pricingResult = await db.query(
      `SELECT * FROM pricing_models WHERE product_id = $1 AND product_version_id IS NULL ORDER BY created_at DESC LIMIT 1`,
      [id]
    )
    if (pricingResult.rows.length > 0) {
      product.pricing = this.mapPricingModel(pricingResult.rows[0])
    }

    return product
  }

  /**
   * Get product by slug
   */
  async getProductBySlug(context: Context, slug: string): Promise<Product | null> {
    const db = getDb()
    const result = await db.query(
      `SELECT 
        p.*,
        pub.id as publisher_id,
        pub.name as publisher_name,
        pub.display_name as publisher_display_name,
        pub.verified as publisher_verified,
        pub.logo_url as publisher_logo_url,
        COALESCE(AVG(pr.rating), 0) as average_rating,
        COUNT(DISTINCT pr.id) as review_count
      FROM products p
      LEFT JOIN publishers pub ON p.publisher_id = pub.id
      LEFT JOIN product_reviews pr ON p.id = pr.product_id
      WHERE p.slug = $1
      GROUP BY p.id, pub.id`,
      [slug]
    )

    if (result.rows.length === 0) return null
    return this.mapProduct(result.rows[0])
  }

  /**
   * Create product
   */
  async createProduct(
    context: Context,
    input: {
      name: string
      slug: string
      category: ProductCategory
      description?: string
      shortDescription?: string
      publisherId: string
      status?: ProductStatus
      featured?: boolean
      iconUrl?: string
      documentationUrl?: string
      supportUrl?: string
      metadata?: Record<string, any>
      tags?: string[]
    }
  ): Promise<Product> {
    const db = getDb()

    // Check if slug exists
    const existing = await db.query(`SELECT id FROM products WHERE slug = $1`, [input.slug])
    if (existing.rows.length > 0) {
      throw new Error(`Product with slug "${input.slug}" already exists`)
    }

    const result = await db.query(
      `INSERT INTO products (
        name, slug, category, description, short_description, publisher_id,
        status, featured, icon_url, documentation_url, support_url, metadata, tags
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
      RETURNING *`,
      [
        input.name,
        input.slug,
        input.category,
        input.description || null,
        input.shortDescription || null,
        input.publisherId,
        input.status || ProductStatus.DRAFT,
        input.featured || false,
        input.iconUrl || null,
        input.documentationUrl || null,
        input.supportUrl || null,
        JSON.stringify(input.metadata || {}),
        input.tags || [],
      ]
    )

    logger.info('Product created', { productId: result.rows[0].id })
    return this.mapProduct(result.rows[0])
  }

  /**
   * Update product
   */
  async updateProduct(
    context: Context,
    id: string,
    input: Partial<{
      name: string
      description: string
      shortDescription: string
      status: ProductStatus
      featured: boolean
      iconUrl: string
      documentationUrl: string
      supportUrl: string
      metadata: Record<string, any>
      tags: string[]
    }>
  ): Promise<Product> {
    const db = getDb()
    const updates: string[] = []
    const params: any[] = []
    let paramIndex = 1

    if (input.name !== undefined) {
      updates.push(`name = $${paramIndex}`)
      params.push(input.name)
      paramIndex++
    }

    if (input.description !== undefined) {
      updates.push(`description = $${paramIndex}`)
      params.push(input.description)
      paramIndex++
    }

    if (input.shortDescription !== undefined) {
      updates.push(`short_description = $${paramIndex}`)
      params.push(input.shortDescription)
      paramIndex++
    }

    if (input.status !== undefined) {
      updates.push(`status = $${paramIndex}`)
      params.push(input.status)
      paramIndex++
    }

    if (input.featured !== undefined) {
      updates.push(`featured = $${paramIndex}`)
      params.push(input.featured)
      paramIndex++
    }

    if (input.iconUrl !== undefined) {
      updates.push(`icon_url = $${paramIndex}`)
      params.push(input.iconUrl)
      paramIndex++
    }

    if (input.documentationUrl !== undefined) {
      updates.push(`documentation_url = $${paramIndex}`)
      params.push(input.documentationUrl)
      paramIndex++
    }

    if (input.supportUrl !== undefined) {
      updates.push(`support_url = $${paramIndex}`)
      params.push(input.supportUrl)
      paramIndex++
    }

    if (input.metadata !== undefined) {
      updates.push(`metadata = $${paramIndex}`)
      params.push(JSON.stringify(input.metadata))
      paramIndex++
    }

    if (input.tags !== undefined) {
      updates.push(`tags = $${paramIndex}`)
      params.push(input.tags)
      paramIndex++
    }

    if (updates.length === 0) {
      return this.getProduct(context, id) as Promise<Product>
    }

    params.push(id)
    const result = await db.query(
      `UPDATE products SET ${updates.join(', ')} WHERE id = $${paramIndex} RETURNING *`,
      params
    )

    logger.info('Product updated', { productId: id })
    return this.mapProduct(result.rows[0])
  }

  /**
   * Create product version
   */
  async createProductVersion(
    context: Context,
    input: {
      productId: string
      version: string
      changelog?: string
      templateId?: string
      status?: ProductVersionStatus
      releasedAt?: Date
      metadata?: Record<string, any>
    }
  ): Promise<ProductVersion> {
    const db = getDb()

    // Check if version exists
    const existing = await db.query(
      `SELECT id FROM product_versions WHERE product_id = $1 AND version = $2`,
      [input.productId, input.version]
    )
    if (existing.rows.length > 0) {
      throw new Error(`Version "${input.version}" already exists for this product`)
    }

    // If this is marked as latest, unmark other versions
    if (input.status === ProductVersionStatus.PUBLISHED) {
      await db.query(
        `UPDATE product_versions SET is_latest = FALSE WHERE product_id = $1`,
        [input.productId]
      )
    }

    const result = await db.query(
      `INSERT INTO product_versions (
        product_id, version, changelog, template_id, status, is_latest, released_at, metadata
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
      RETURNING *`,
      [
        input.productId,
        input.version,
        input.changelog || null,
        input.templateId || null,
        input.status || ProductVersionStatus.DRAFT,
        input.status === ProductVersionStatus.PUBLISHED,
        input.releasedAt || new Date(),
        JSON.stringify(input.metadata || {}),
      ]
    )

    logger.info('Product version created', { versionId: result.rows[0].id })
    return this.mapProductVersion(result.rows[0])
  }

  /**
   * Create pricing model
   */
  async createPricingModel(
    context: Context,
    input: {
      productId: string
      productVersionId?: string
      pricingType: PricingType
      basePrice?: number
      currency?: string
      billingPeriod?: BillingPeriod
      usageRates?: Record<string, any>
      metadata?: Record<string, any>
    }
  ): Promise<PricingModel> {
    const db = getDb()
    const result = await db.query(
      `INSERT INTO pricing_models (
        product_id, product_version_id, pricing_type, base_price, currency, billing_period, usage_rates, metadata
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
      RETURNING *`,
      [
        input.productId,
        input.productVersionId || null,
        input.pricingType,
        input.basePrice || null,
        input.currency || 'USD',
        input.billingPeriod || null,
        JSON.stringify(input.usageRates || {}),
        JSON.stringify(input.metadata || {}),
      ]
    )

    logger.info('Pricing model created', { pricingModelId: result.rows[0].id })
    return this.mapPricingModel(result.rows[0])
  }

  /**
   * Get product reviews
   */
  async getProductReviews(
    context: Context,
    productId: string
  ): Promise<ProductReview[]> {
    const db = getDb()
    const result = await db.query(
      `SELECT * FROM product_reviews WHERE product_id = $1 ORDER BY created_at DESC`,
      [productId]
    )
    return result.rows.map(this.mapProductReview)
  }

  /**
   * Create product review
   */
  async createProductReview(
    context: Context,
    input: {
      productId: string
      rating: number
      title?: string
      reviewText?: string
      verifiedPurchase?: boolean
    }
  ): Promise<ProductReview> {
    if (!context.user) {
      throw new Error('Authentication required')
    }

    const db = getDb()

    // Check if user already reviewed
    const existing = await db.query(
      `SELECT id FROM product_reviews WHERE product_id = $1 AND user_id = $2`,
      [input.productId, context.user.id]
    )
    if (existing.rows.length > 0) {
      throw new Error('You have already reviewed this product')
    }

    const result = await db.query(
      `INSERT INTO product_reviews (
        product_id, user_id, rating, title, review_text, verified_purchase
      ) VALUES ($1, $2, $3, $4, $5, $6)
      RETURNING *`,
      [
        input.productId,
        context.user.id,
        input.rating,
        input.title || null,
        input.reviewText || null,
        input.verifiedPurchase || false,
      ]
    )

    logger.info('Product review created', { reviewId: result.rows[0].id })
    return this.mapProductReview(result.rows[0])
  }

  // Mapper functions
  private mapPublisher(row: any): Publisher {
    return {
      id: row.id,
      name: row.name,
      displayName: row.display_name,
      description: row.description,
      websiteUrl: row.website_url,
      logoUrl: row.logo_url,
      verified: row.verified,
      metadata: row.metadata || {},
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    }
  }

  private mapProduct(row: any): Product {
    return {
      id: row.id,
      name: row.name,
      slug: row.slug,
      category: row.category as ProductCategory,
      description: row.description,
      shortDescription: row.short_description,
      publisherId: row.publisher_id,
      publisher: row.publisher_id
        ? {
            id: row.publisher_id,
            name: row.publisher_name,
            displayName: row.publisher_display_name,
            verified: row.publisher_verified,
            logoUrl: row.publisher_logo_url,
            metadata: {},
            createdAt: new Date(),
            updatedAt: new Date(),
          }
        : undefined,
      status: row.status as ProductStatus,
      featured: row.featured,
      iconUrl: row.icon_url,
      documentationUrl: row.documentation_url,
      supportUrl: row.support_url,
      metadata: row.metadata || {},
      tags: row.tags || [],
      createdAt: row.created_at,
      updatedAt: row.updated_at,
      averageRating: row.average_rating ? parseFloat(row.average_rating) : undefined,
      reviewCount: row.review_count ? parseInt(row.review_count) : undefined,
    }
  }

  private mapProductVersion(row: any): ProductVersion {
    return {
      id: row.id,
      productId: row.product_id,
      version: row.version,
      changelog: row.changelog,
      templateId: row.template_id,
      status: row.status as ProductVersionStatus,
      isLatest: row.is_latest,
      releasedAt: row.released_at,
      metadata: row.metadata || {},
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    }
  }

  private mapPricingModel(row: any): PricingModel {
    return {
      id: row.id,
      productId: row.product_id,
      productVersionId: row.product_version_id,
      pricingType: row.pricing_type as PricingType,
      basePrice: row.base_price ? parseFloat(row.base_price) : undefined,
      currency: row.currency,
      billingPeriod: row.billing_period as BillingPeriod,
      usageRates: row.usage_rates || {},
      metadata: row.metadata || {},
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    }
  }

  private mapProductReview(row: any): ProductReview {
    return {
      id: row.id,
      productId: row.product_id,
      userId: row.user_id,
      rating: row.rating,
      title: row.title,
      reviewText: row.review_text,
      verifiedPurchase: row.verified_purchase,
      helpfulCount: row.helpful_count,
      createdAt: row.created_at,
      updatedAt: row.updated_at,
    }
  }
}

export const catalogService = new CatalogService()

