/**
 * Marketplace REST Endpoints
 *
 * Author Endpoints:      /marketplace/listings/*
 * Consumer Endpoints:    /marketplace/listings/:id/install, /marketplace/listings/:id/reviews, etc.
 * Public Endpoints:      /public/marketplace/*
 * Admin Endpoints:       /marketplace/admin/*
 * Report Endpoints:      /marketplace/listings/:id/report, /marketplace/reports/mine
 */

import type { FastifyInstance } from 'fastify';
import { getRequestProductId } from '../../lib/request-context.js';
import { BadRequestError, NotFoundError, ForbiddenError, UnauthorizedError } from '../../lib/errors.js';
import * as repo from './repository.js';
import {
  CreateListingSchema,
  UpdateListingSchema,
  SubmitForCertificationSchema,
  CertificationActionSchema,
  ListListingsQuerySchema,
  CreateReviewSchema,
  UpdateReviewSchema,
  ListReviewsQuerySchema,
  ListInstallsQuerySchema,
  CreateReportSchema,
  ResolveReportSchema,
  ListReportsQuerySchema,
  type MarketplaceListingDoc,
  type MarketplaceReviewDoc,
  type MarketplaceInstallDoc,
  type MarketplaceCertificationDoc,
  type MarketplaceReportDoc,
  type MarketplaceVoteDoc,
} from './types.js';

// ─────────────────────────────────────────────────────────────────────────────
// Helper Functions
// ─────────────────────────────────────────────────────────────────────────────

function generateId(prefix: string): string {
  return `${prefix}_${crypto.randomUUID()}`;
}

function requireAuth(req: { jwtPayload?: { sub: string; role?: string } }): string {
  if (!req.jwtPayload?.sub) throw new UnauthorizedError('Authentication required');
  return req.jwtPayload.sub;
}

function requireAdmin(req: { jwtPayload?: { sub: string; role?: string } }): string {
  const sub = requireAuth(req);
  if (req.jwtPayload?.role !== 'admin') throw new ForbiddenError('Admin access required');
  return sub;
}

// ─────────────────────────────────────────────────────────────────────────────
// Author Routes (Authenticated)
// ─────────────────────────────────────────────────────────────────────────────

async function authorRoutes(app: FastifyInstance) {
  // Create draft listing
  app.post('/marketplace/listings', async (req, reply) => {
    const userId = requireAuth(req);
    const parsed = CreateListingSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const input = parsed.data;
    const pid = input.productId || getRequestProductId(req);
    const now = new Date().toISOString();

    const doc: MarketplaceListingDoc = {
      id: generateId('lst'),
      productId: pid,
      templateType: input.templateType,
      authorId: userId,
      authorName: 'Anonymous', // Could be enhanced with user profile lookup
      authorAvatarUrl: null,
      title: input.title,
      shortDescription: input.shortDescription,
      description: input.description,
      tags: input.tags,
      category: input.category,
      screenshots: input.screenshots,
      previewUrl: input.previewUrl,
      payload: input.payload,
      payloadVersion: input.payloadVersion,
      pricingModel: input.pricingModel,
      priceInCents: input.priceInCents,
      currency: input.currency,
      certificationStatus: 'draft',
      certificationNotes: null,
      certifiedAt: null,
      certifiedBy: null,
      installCount: 0,
      reviewCount: 0,
      averageRating: 0,
      voteCount: 0,
      version: input.version,
      previousVersionId: null,
      visibility: input.visibility,
      featured: false,
      createdAt: now,
      updatedAt: now,
      publishedAt: null,
    };

    const created = await repo.createListing(doc);
    reply.code(201);
    return created;
  });

  // List my listings
  app.get('/marketplace/listings/mine', async req => {
    const userId = requireAuth(req);
    const pid = getRequestProductId(req);
    const listings = await repo.listMyListings(userId, pid);
    return { listings };
  });

  // Get listing detail (author view - can see private)
  app.get('/marketplace/listings/:id', async req => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');

    // Only author or admin can view non-public listings
    if (listing.authorId !== userId && listing.visibility !== 'public') {
      throw new NotFoundError('Listing not found');
    }

    return listing;
  });

  // Update listing (only draft or rejected)
  app.put('/marketplace/listings/:id', async req => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const existing = await repo.getListingById(id);
    if (!existing) throw new NotFoundError('Listing not found');
    if (existing.authorId !== userId) throw new ForbiddenError('Not your listing');

    // Only allow edits in draft or rejected state
    if (existing.certificationStatus !== 'draft' && existing.certificationStatus !== 'rejected') {
      throw new BadRequestError('Cannot edit listing in current state');
    }

    const parsed = UpdateListingSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const updated = await repo.updateListing(id, parsed.data);
    if (!updated) throw new NotFoundError('Update failed');
    return updated;
  });

  // Submit for certification
  app.post('/marketplace/listings/:id/submit', async req => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const existing = await repo.getListingById(id);
    if (!existing) throw new NotFoundError('Listing not found');
    if (existing.authorId !== userId) throw new ForbiddenError('Not your listing');

    const parsed = SubmitForCertificationSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    // Can only submit from draft or rejected
    if (existing.certificationStatus !== 'draft' && existing.certificationStatus !== 'rejected') {
      throw new BadRequestError('Listing already submitted or approved');
    }

    // Auto-validate: at least one screenshot required
    if (existing.screenshots.length === 0) {
      throw new BadRequestError('At least one screenshot is required');
    }

    const now = new Date().toISOString();
    const updated = await repo.updateListing(id, {
      certificationStatus: 'submitted',
      updatedAt: now,
    });

    // Create certification audit entry
    const certDoc: MarketplaceCertificationDoc = {
      id: generateId('cert'),
      listingId: id,
      productId: existing.productId,
      action: existing.certificationStatus === 'rejected' ? 'resubmitted' : 'submitted',
      performedBy: userId,
      notes: parsed.data.notes || '',
      automatedChecks: null,
      createdAt: now,
    };
    await repo.createCertification(certDoc);

    return updated;
  });

  // Publish approved listing
  app.post('/marketplace/listings/:id/publish', async req => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const existing = await repo.getListingById(id);
    if (!existing) throw new NotFoundError('Listing not found');
    if (existing.authorId !== userId) throw new ForbiddenError('Not your listing');

    if (existing.certificationStatus !== 'approved') {
      throw new BadRequestError('Listing must be approved before publishing');
    }

    const now = new Date().toISOString();
    const updated = await repo.updateListing(id, {
      visibility: 'public',
      publishedAt: now,
      updatedAt: now,
    });

    return updated;
  });

  // Delete listing
  app.delete('/marketplace/listings/:id', async req => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const existing = await repo.getListingById(id);
    if (!existing) throw new NotFoundError('Listing not found');
    if (existing.authorId !== userId) throw new ForbiddenError('Not your listing');

    await repo.deleteListing(id);
    return { success: true };
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// Consumer Routes (Authenticated)
// ─────────────────────────────────────────────────────────────────────────────

async function consumerRoutes(app: FastifyInstance) {
  // Install a listing
  app.post('/marketplace/listings/:id/install', async (req, reply) => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');
    if (listing.certificationStatus !== 'approved' || listing.visibility !== 'public') {
      throw new NotFoundError('Listing not found');
    }

    // Check if already installed
    const existing = await repo.getInstallByUserAndListing(userId, id);
    if (existing && existing.uninstalledAt === null) {
      throw new BadRequestError('Already installed');
    }

    const now = new Date().toISOString();
    const doc: MarketplaceInstallDoc = {
      id: generateId('inst'),
      listingId: id,
      productId: listing.productId,
      userId: userId,
      version: listing.version,
      installedAt: now,
      uninstalledAt: null,
    };

    const created = await repo.createInstall(doc);

    // Update listing install count
    await repo.updateListingStats(id, { installCount: listing.installCount + 1 });

    reply.code(201);
    return { install: created, payload: listing.payload };
  });

  // Uninstall
  app.delete('/marketplace/listings/:id/install', async req => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');

    const install = await repo.getInstallByUserAndListing(userId, id);
    if (!install || install.uninstalledAt !== null) {
      throw new NotFoundError('Install not found');
    }

    await repo.uninstall(install.id);

    // Decrement install count
    await repo.updateListingStats(id, { installCount: Math.max(0, listing.installCount - 1) });

    return { success: true };
  });

  // List my installs
  app.get('/marketplace/installs', async req => {
    const userId = requireAuth(req);
    const parsed = ListInstallsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const query = parsed.data;
    if (!query.productId) query.productId = getRequestProductId(req);

    const { items, total } = await repo.listInstalls(userId, query);
    return { items, total, limit: query.limit, offset: query.offset };
  });

  // Add review (must have installed)
  app.post('/marketplace/listings/:id/reviews', async (req, reply) => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');

    // Must have installed to review
    const hasInstalled = await repo.hasUserInstalled(userId, id);
    if (!hasInstalled) {
      throw new ForbiddenError('Must install template before reviewing');
    }

    // Check if already reviewed
    const existingReview = await repo.getReviewByUserAndListing(userId, id);
    if (existingReview) {
      throw new BadRequestError('Already reviewed. Use PUT to update.');
    }

    const parsed = CreateReviewSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const now = new Date().toISOString();
    const doc: MarketplaceReviewDoc = {
      id: generateId('rev'),
      listingId: id,
      productId: listing.productId,
      authorId: userId,
      authorName: 'Anonymous',
      rating: parsed.data.rating,
      title: parsed.data.title,
      body: parsed.data.body,
      helpful: 0,
      verified: true,
      createdAt: now,
      updatedAt: now,
    };

    const created = await repo.createReview(doc);

    // Update listing review stats
    const newReviewCount = listing.reviewCount + 1;
    const newAvgRating =
      (listing.averageRating * listing.reviewCount + parsed.data.rating) / newReviewCount;
    await repo.updateListingStats(id, {
      reviewCount: newReviewCount,
      averageRating: Math.round(newAvgRating * 100) / 100,
    });

    reply.code(201);
    return created;
  });

  // Edit my review
  app.put('/marketplace/reviews/:id', async req => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const review = await repo.getReviewById(id);
    if (!review) throw new NotFoundError('Review not found');
    if (review.authorId !== userId) throw new ForbiddenError('Not your review');

    const parsed = UpdateReviewSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const updates = parsed.data;
    const oldRating = review.rating;

    const updated = await repo.updateReview(id, updates);
    if (!updated) throw new NotFoundError('Update failed');

    // Recalculate listing average if rating changed
    if (updates.rating !== undefined && updates.rating !== oldRating) {
      const listing = await repo.getListingById(review.listingId);
      if (listing && listing.reviewCount > 0) {
        const currentSum = listing.averageRating * listing.reviewCount;
        const newSum = currentSum - oldRating + updates.rating;
        const newAvg = newSum / listing.reviewCount;
        await repo.updateListingStats(review.listingId, {
          averageRating: Math.round(newAvg * 100) / 100,
        });
      }
    }

    return updated;
  });

  // Delete my review
  app.delete('/marketplace/reviews/:id', async req => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const review = await repo.getReviewById(id);
    if (!review) throw new NotFoundError('Review not found');
    if (review.authorId !== userId) throw new ForbiddenError('Not your review');

    await repo.deleteReview(id);

    // Update listing stats
    const listing = await repo.getListingById(review.listingId);
    if (listing && listing.reviewCount > 0) {
      const newCount = listing.reviewCount - 1;
      const currentSum = listing.averageRating * listing.reviewCount;
      const newAvg = newCount > 0 ? (currentSum - review.rating) / newCount : 0;
      await repo.updateListingStats(review.listingId, {
        reviewCount: newCount,
        averageRating: Math.round(newAvg * 100) / 100,
      });
    }

    return { success: true };
  });

  // Vote on listing (toggle upvote) - prevents duplicate votes
  app.post('/marketplace/listings/:id/vote', async req => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');
    if (listing.certificationStatus !== 'approved' || listing.visibility !== 'public') {
      throw new NotFoundError('Listing not found');
    }

    // Check if user already voted
    const existingVote = await repo.getVoteByUserAndListing(userId, id);
    if (existingVote) {
      // Toggle off: remove vote and decrement count
      await repo.deleteVote(existingVote.id);
      const newVoteCount = Math.max(0, listing.voteCount - 1);
      await repo.updateListingStats(id, { voteCount: newVoteCount });
      return { voted: false, voteCount: newVoteCount };
    }

    // Create new vote
    const now = new Date().toISOString();
    const voteDoc: MarketplaceVoteDoc = {
      id: generateId('vote'),
      listingId: id,
      productId: listing.productId,
      userId: userId,
      createdAt: now,
    };
    await repo.createVote(voteDoc);

    // Increment vote count
    const newVoteCount = listing.voteCount + 1;
    await repo.updateListingStats(id, { voteCount: newVoteCount });

    return { voted: true, voteCount: newVoteCount };
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// Public Routes (No Auth Required)
// ─────────────────────────────────────────────────────────────────────────────

async function publicRoutes(app: FastifyInstance) {
  // Browse catalog
  app.get('/public/marketplace', async req => {
    const parsed = ListListingsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const query = parsed.data;
    if (!query.productId) {
      throw new BadRequestError('productId query parameter is required');
    }

    const { items, total } = await repo.listListings(query);
    return { items, total, limit: query.limit, offset: query.offset };
  });

  // Get public listing detail
  app.get('/public/marketplace/:id', async req => {
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');
    if (listing.certificationStatus !== 'approved' || listing.visibility !== 'public') {
      throw new NotFoundError('Listing not found');
    }

    // Return listing without full payload (just preview)
    const { payload: _payload, ...publicListing } = listing;
    void _payload; // Mark as intentionally unused
    return { ...publicListing, hasPayload: true };
  });

  // Get reviews for a listing
  app.get('/public/marketplace/:id/reviews', async req => {
    const { id } = req.params as { id: string };
    const parsed = ListReviewsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');

    const query = parsed.data;
    const { items, total } = await repo.listReviews(id, query);
    return { items, total, limit: query.limit, offset: query.offset };
  });

  // Featured/trending listings
  app.get('/public/marketplace/featured', async req => {
    const pid = getRequestProductId(req);
    const limit = Math.min(parseInt((req.query as Record<string, string>).limit || '10', 10), 50);
    const items = await repo.listFeaturedListings(pid, limit);
    return { items };
  });

  // List categories for a product
  app.get('/public/marketplace/categories', async req => {
    const pid = getRequestProductId(req);
    const categories = await repo.listCategories(pid);
    return { categories };
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// Admin Routes (Admin Auth Required)
// ─────────────────────────────────────────────────────────────────────────────

async function adminRoutes(app: FastifyInstance) {
  // List pending certification
  app.get('/marketplace/admin/pending', async req => {
    requireAdmin(req);
    const pid = getRequestProductId(req);
    const items = await repo.listPendingCertification(pid);
    return { items };
  });

  // Approve listing
  app.post('/marketplace/admin/:id/approve', async req => {
    const adminId = requireAdmin(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');

    const parsed = CertificationActionSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const now = new Date().toISOString();
    const updated = await repo.updateListing(id, {
      certificationStatus: 'approved',
      certifiedAt: now,
      certifiedBy: adminId,
      certificationNotes: parsed.data.notes || null,
      updatedAt: now,
    });

    // Create audit entry
    const certDoc: MarketplaceCertificationDoc = {
      id: generateId('cert'),
      listingId: id,
      productId: listing.productId,
      action: 'approved',
      performedBy: adminId,
      notes: parsed.data.notes || '',
      automatedChecks: parsed.data.automatedChecks || null,
      createdAt: now,
    };
    await repo.createCertification(certDoc);

    return updated;
  });

  // Reject listing
  app.post('/marketplace/admin/:id/reject', async req => {
    const adminId = requireAdmin(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');

    const parsed = CertificationActionSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    if (!parsed.data.notes) {
      throw new BadRequestError('Rejection requires notes explaining why');
    }

    const now = new Date().toISOString();
    const updated = await repo.updateListing(id, {
      certificationStatus: 'rejected',
      certificationNotes: parsed.data.notes,
      updatedAt: now,
    });

    const certDoc: MarketplaceCertificationDoc = {
      id: generateId('cert'),
      listingId: id,
      productId: listing.productId,
      action: 'rejected',
      performedBy: adminId,
      notes: parsed.data.notes,
      automatedChecks: parsed.data.automatedChecks || null,
      createdAt: now,
    };
    await repo.createCertification(certDoc);

    return updated;
  });

  // Suspend published listing
  app.post('/marketplace/admin/:id/suspend', async req => {
    const adminId = requireAdmin(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');

    const parsed = CertificationActionSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const now = new Date().toISOString();
    const updated = await repo.updateListing(id, {
      certificationStatus: 'suspended',
      visibility: 'unlisted',
      certificationNotes: parsed.data.notes || 'Suspended by admin',
      updatedAt: now,
    });

    const certDoc: MarketplaceCertificationDoc = {
      id: generateId('cert'),
      listingId: id,
      productId: listing.productId,
      action: 'suspended',
      performedBy: adminId,
      notes: parsed.data.notes || 'Suspended by admin',
      automatedChecks: null,
      createdAt: now,
    };
    await repo.createCertification(certDoc);

    return updated;
  });

  // Toggle featured flag
  app.post('/marketplace/admin/:id/feature', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');

    const updated = await repo.updateListing(id, {
      featured: !listing.featured,
      updatedAt: new Date().toISOString(),
    });

    return { featured: updated?.featured };
  });

  // Get marketplace stats
  app.get('/marketplace/admin/stats', async req => {
    requireAdmin(req);
    const pid = getRequestProductId(req);
    const stats = await repo.getMarketplaceStats(pid);
    return stats;
  });

  // List all reports
  app.get('/marketplace/admin/reports', async req => {
    requireAdmin(req);
    const parsed = ListReportsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const query = parsed.data;
    const { items, total } = await repo.listReports(query);
    return { items, total, limit: query.limit, offset: query.offset };
  });

  // Resolve a report
  app.post('/marketplace/admin/reports/:id/resolve', async req => {
    const adminId = requireAdmin(req);
    const { id } = req.params as { id: string };

    const report = await repo.getReportById(id);
    if (!report) throw new NotFoundError('Report not found');

    const parsed = ResolveReportSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const updated = await repo.updateReport(id, {
      status: parsed.data.status,
      resolvedBy: adminId,
      resolutionNotes: parsed.data.resolutionNotes,
    });

    return updated;
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// Report Routes (Authenticated)
// ─────────────────────────────────────────────────────────────────────────────

async function reportRoutes(app: FastifyInstance) {
  // Report a listing
  app.post('/marketplace/listings/:id/report', async (req, reply) => {
    const userId = requireAuth(req);
    const { id } = req.params as { id: string };

    const listing = await repo.getListingById(id);
    if (!listing) throw new NotFoundError('Listing not found');

    const parsed = CreateReportSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const now = new Date().toISOString();
    const doc: MarketplaceReportDoc = {
      id: generateId('rpt'),
      listingId: id,
      productId: listing.productId,
      reporterId: userId,
      reason: parsed.data.reason,
      details: parsed.data.details,
      status: 'open',
      resolvedBy: null,
      resolutionNotes: null,
      createdAt: now,
      resolvedAt: null,
    };

    const created = await repo.createReport(doc);
    reply.code(201);
    return created;
  });

  // List my submitted reports
  app.get('/marketplace/reports/mine', async req => {
    const userId = requireAuth(req);
    const pid = getRequestProductId(req);
    const reports = await repo.listMyReports(userId, { productId: pid });
    return { reports };
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// Main Export
// ─────────────────────────────────────────────────────────────────────────────

export async function marketplaceRoutes(app: FastifyInstance) {
  await app.register(authorRoutes, { prefix: '/api' });
  await app.register(consumerRoutes, { prefix: '/api' });
  await app.register(publicRoutes, { prefix: '/api' });
  await app.register(adminRoutes, { prefix: '/api' });
  await app.register(reportRoutes, { prefix: '/api' });
}
