import type { Page, RatingAggregate, Review, ReviewRating, ReviewStatus } from '@platform-modules/commerce-reviews'
import { ReviewWireError } from './errors.js'

export type PublicReview = Omit<Review, 'userId'> & { userId?: string }

function asObject(v: unknown, ctx: string): Record<string, unknown> {
  if (typeof v !== 'object' || v === null) {
    throw new ReviewWireError(`${ctx}: expected an object, got ${v === null ? 'null' : typeof v}`)
  }
  return v as Record<string, unknown>
}

export function reviveRequiredDate(v: unknown, ctx: string): Date {
  if (v instanceof Date) return v
  if (typeof v === 'string' || typeof v === 'number') {
    const d = new Date(v)
    if (Number.isNaN(d.getTime())) throw new ReviewWireError(`reviveReview: ${ctx} is not a valid date: ${String(v)}`)
    return d
  }
  throw new ReviewWireError(`reviveReview: ${ctx} is required`)
}

const RATINGS: ReviewRating[] = [1, 2, 3, 4, 5]
const STATUSES: ReviewStatus[] = ['pending', 'approved', 'rejected']

/** Wire JSON → typed PublicReview. §5 — userId optional per §1.3 PII-strip. */
export function reviveReview(raw: unknown): PublicReview {
  const r = asObject(raw, 'reviveReview')

  if (typeof r.id !== 'string') throw new ReviewWireError('reviveReview: id must be a string')
  if (typeof r.productId !== 'string') throw new ReviewWireError('reviveReview: productId must be a string')

  if (r.userId !== undefined && r.userId !== null && typeof r.userId !== 'string') {
    throw new ReviewWireError('reviveReview: userId must be a string if present')
  }

  if (r.vendorId !== null && typeof r.vendorId !== 'string') {
    throw new ReviewWireError('reviveReview: vendorId must be a string or null')
  }

  if (!RATINGS.includes(r.rating as ReviewRating)) {
    throw new ReviewWireError('reviveReview: rating must be 1, 2, 3, 4, or 5')
  }

  if (r.body !== null && typeof r.body !== 'string') {
    throw new ReviewWireError('reviveReview: body must be a string or null')
  }

  if (!STATUSES.includes(r.status as ReviewStatus)) {
    throw new ReviewWireError('reviveReview: status must be pending, approved, or rejected')
  }

  if (r.vendorReply !== null && typeof r.vendorReply !== 'string') {
    throw new ReviewWireError('reviveReview: vendorReply must be a string or null')
  }

  const createdAt = reviveRequiredDate(r.createdAt, 'createdAt')
  const updatedAt = reviveRequiredDate(r.updatedAt, 'updatedAt')

  return {
    id: r.id,
    productId: r.productId,
    ...(r.userId != null ? { userId: r.userId } : {}),
    vendorId: r.vendorId as string | null,
    rating: r.rating as ReviewRating,
    body: r.body as string | null,
    status: r.status as ReviewStatus,
    vendorReply: r.vendorReply as string | null,
    createdAt,
    updatedAt,
  } as PublicReview
}

/** Wire JSON → typed Page<PublicReview>. §5 */
export function reviveReviewPage(raw: unknown): Page<PublicReview> {
  const r = asObject(raw, 'reviveReviewPage')
  if (!Array.isArray(r.items)) throw new ReviewWireError('reviveReviewPage: items must be an array')
  return {
    items: r.items.map((it) => reviveReview(it)),
    nextCursor: typeof r.nextCursor === 'string' ? r.nextCursor : null,
  }
}

/** Wire JSON → typed RatingAggregate. §5 — validation only, no transform. */
export function reviveRatingAggregate(raw: unknown): RatingAggregate {
  const r = asObject(raw, 'reviveRatingAggregate')
  if (typeof r.productId !== 'string') throw new ReviewWireError('reviveRatingAggregate: productId must be a string')

  if (typeof r.avg !== 'number' || !Number.isFinite(r.avg)) {
    throw new ReviewWireError('reviveRatingAggregate: avg must be a finite number')
  }
  if (typeof r.count !== 'number' || !Number.isSafeInteger(r.count) || r.count < 0) {
    throw new ReviewWireError('reviveRatingAggregate: count must be a non-negative safe integer')
  }

  if (!r.distribution || typeof r.distribution !== 'object' || Array.isArray(r.distribution)) {
    throw new ReviewWireError('reviveRatingAggregate: distribution must be an object')
  }

  const distribution = r.distribution as Record<string, unknown>
  const requiredKeys: ReviewRating[] = [1, 2, 3, 4, 5]

  for (const key of requiredKeys) {
    if (typeof distribution[key] !== 'number' || !Number.isSafeInteger(distribution[key]) || (distribution[key] as number) < 0) {
      throw new ReviewWireError(`reviveRatingAggregate: distribution[${key}] must be a non-negative safe integer`)
    }
  }

  return {
    productId: r.productId,
    avg: r.avg,
    count: r.count,
    distribution: distribution as Record<ReviewRating, number>,
  }
}
