import { Request, Response, NextFunction } from "express";
import { catchAsyncError } from "../../middlewares/catchAsyncError";
import ErrorHandler from "../../middlewares/error";
import {
  productUpdateService,
  UpdateCreateData,
  UpdateFilters,
} from "./product.update.service";
import { AuthRequest } from "../../middlewares/auth";
import { database } from "../../config/db";

export const productUpdateController = {
  // Create new product update
  createUpdate: catchAsyncError(
    async (req: AuthRequest, res: Response, next: NextFunction) => {
      const userId = req.user?.id;

      if (!userId) {
        return next(new ErrorHandler("Authentication required", 401));
      }

      const data: UpdateCreateData = {
        ...req.body,
        created_by: userId,
      };

      // Validate version type
      const validVersionTypes = ["major", "minor", "patch", "beta", "alpha"];
      if (data.version_type && !validVersionTypes.includes(data.version_type)) {
        return next(
          new ErrorHandler(
            `Invalid version type. Must be one of: ${validVersionTypes.join(", ")}`,
            400,
          ),
        );
      }

      try {
        const update = await productUpdateService.createUpdate(data);

        // Emit socket event for real-time updates
        if (req.app.get("io") && data.status === "published") {
          const io = req.app.get("io");
          io.to(`product:${data.product_id}`).emit("update:published", {
            update,
            product_id: data.product_id,
          });
        }

        res.status(201).json({
          success: true,
          message: "Product update created successfully",
          data: update,
        });
      } catch (error: any) {
        return next(new ErrorHandler(error.message, 400));
      }
    },
  ),

  // Get update by ID
  getUpdateById: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { id } = req.params;
      const incrementViews = req.query.views !== "false";

      const update = await productUpdateService.getUpdateById(
        id,
        incrementViews,
      );

      if (!update) {
        return next(new ErrorHandler("Update not found", 404));
      }

      // Check if update is public or user has access
      if (!update.is_public && update.status !== "published") {
        const userId = (req as AuthRequest).user?.id;
        if (!userId || userId !== update.created_by) {
          return next(
            new ErrorHandler("Update is not publicly available", 403),
          );
        }
      }

      res.status(200).json({
        success: true,
        data: update,
      });
    },
  ),

  // Get update by slug
  getUpdateBySlug: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { slug } = req.params;
      const incrementViews = req.query.views !== "false";

      const update = await productUpdateService.getUpdateBySlug(
        slug,
        incrementViews,
      );

      if (!update) {
        return next(new ErrorHandler("Update not found", 404));
      }

      if (!update.is_public && update.status !== "published") {
        const userId = (req as AuthRequest).user?.id;
        if (!userId || userId !== update.created_by) {
          return next(
            new ErrorHandler("Update is not publicly available", 403),
          );
        }
      }

      res.status(200).json({
        success: true,
        data: update,
      });
    },
  ),

  // Get all updates
  getAllUpdates: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const {
        page = 1,
        limit = 20,
        product_id,
        created_by,
        version_type,
        status = "published",
        is_public = true,
        is_latest,
        is_beta,
        search,
        from_date,
        to_date,
        sort_by = "release_date",
        order = "desc",
      } = req.query;

      const filters: UpdateFilters = {
        page: parseInt(page as string),
        limit: parseInt(limit as string),
        product_id: product_id as string,
        created_by: created_by as string,
        version_type: version_type as string,
        status: status as string,
        is_public: is_public === "true",
        is_latest: is_latest ? is_latest === "true" : undefined,
        is_beta: is_beta ? is_beta === "true" : undefined,
        search: search as string,
        from_date: from_date ? new Date(from_date as string) : undefined,
        to_date: to_date ? new Date(to_date as string) : undefined,
        sort_by: sort_by as string,
        order: order as "asc" | "desc",
      };

      const result = await productUpdateService.getAllUpdates(filters);

      res.status(200).json({
        success: true,
        ...result,
      });
    },
  ),

  // Get product's updates
  getProductUpdates: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { productId } = req.params;
      const {
        page = 1,
        limit = 20,
        version_type,
        status = "published",
        is_public = true,
        is_latest,
        is_beta,
        search,
        sort_by = "release_date",
        order = "desc",
      } = req.query;

      const filters: UpdateFilters = {
        page: parseInt(page as string),
        limit: parseInt(limit as string),
        product_id: productId,
        version_type: version_type as string,
        status: status as string,
        is_public: is_public === "true",
        is_latest: is_latest ? is_latest === "true" : undefined,
        is_beta: is_beta ? is_beta === "true" : undefined,
        search: search as string,
        sort_by: sort_by as string,
        order: order as "asc" | "desc",
      };

      const result = await productUpdateService.getProductUpdates(
        productId,
        filters,
      );

      res.status(200).json({
        success: true,
        ...result,
      });
    },
  ),

  // Get latest update for a product
  getLatestUpdate: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { productId } = req.params;

      const latestUpdate =
        await productUpdateService.getLatestUpdate(productId);

      if (!latestUpdate) {
        return next(new ErrorHandler("No published updates found", 404));
      }

      res.status(200).json({
        success: true,
        data: latestUpdate,
      });
    },
  ),

  // Update product update
  updateUpdate: catchAsyncError(
    async (req: AuthRequest, res: Response, next: NextFunction) => {
      const { id } = req.params;
      const userId = req.user?.id;
      const data = req.body;

      if (!userId) {
        return next(new ErrorHandler("Authentication required", 401));
      }

      try {
        const updatedUpdate = await productUpdateService.updateUpdate(
          id,
          data,
          userId,
        );

        res.status(200).json({
          success: true,
          message: "Update updated successfully",
          data: updatedUpdate,
        });
      } catch (error: any) {
        return next(
          new ErrorHandler(
            error.message,
            error.message.includes("permission") ? 403 : 400,
          ),
        );
      }
    },
  ),

  // Delete update
  deleteUpdate: catchAsyncError(
    async (req: AuthRequest, res: Response, next: NextFunction) => {
      const { id } = req.params;
      const userId = req.user?.id;

      if (!userId) {
        return next(new ErrorHandler("Authentication required", 401));
      }

      try {
        const deletedUpdate = await productUpdateService.deleteUpdate(
          id,
          userId,
        );

        res.status(200).json({
          success: true,
          message: "Update deleted successfully",
          data: deletedUpdate,
        });
      } catch (error: any) {
        return next(
          new ErrorHandler(
            error.message,
            error.message.includes("permission") ? 403 : 400,
          ),
        );
      }
    },
  ),

  // Upvote an update
  upvoteUpdate: catchAsyncError(
    async (req: AuthRequest, res: Response, next: NextFunction) => {
      const { id } = req.params;
      const userId = req.user?.id;

      if (!userId) {
        return next(new ErrorHandler("Authentication required", 401));
      }

      try {
        const result = await productUpdateService.upvoteUpdate(id, userId);

        res.status(200).json({
          success: true,
          message: "Update upvoted successfully",
          data: { upvotes: result.upvotes },
        });
      } catch (error: any) {
        return next(new ErrorHandler(error.message, 400));
      }
    },
  ),

  // Get update statistics
  getUpdateStats: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { productId } = req.params;

      const stats = await productUpdateService.getUpdateStats(productId);

      res.status(200).json({
        success: true,
        data: stats,
      });
    },
  ),

  // Get version history
  getVersionHistory: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { productId } = req.params;

      const versionHistory =
        await productUpdateService.getVersionHistory(productId);

      res.status(200).json({
        success: true,
        data: versionHistory,
      });
    },
  ),

  // Get recent updates
  getRecentUpdates: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const limit = parseInt(req.query.limit as string) || 10;

      const recentUpdates = await productUpdateService.getRecentUpdates(limit);

      res.status(200).json({
        success: true,
        data: recentUpdates,
      });
    },
  ),

  // Get updates by version type
  getUpdatesByType: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { productId, type } = req.params;

      const validTypes = ["major", "minor", "patch", "beta", "alpha"];
      if (!validTypes.includes(type)) {
        return next(
          new ErrorHandler(
            `Invalid version type. Must be one of: ${validTypes.join(", ")}`,
            400,
          ),
        );
      }

      const updates = await productUpdateService.getUpdatesByType(
        productId,
        type,
      );

      res.status(200).json({
        success: true,
        data: updates,
      });
    },
  ),

  // Get changelog
  getChangelog: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { productId } = req.params;
      const limit = parseInt(req.query.limit as string) || 20;

      const changelog = await productUpdateService.getChangelog(
        productId,
        limit,
      );

      res.status(200).json({
        success: true,
        data: changelog,
      });
    },
  ),

  // Get new updates since last viewed
  getNewUpdatesSince: catchAsyncError(
    async (req: AuthRequest, res: Response, next: NextFunction) => {
      const { productId } = req.params;
      const userId = req.user?.id;
      const { last_viewed } = req.query;

      if (!userId) {
        return next(new ErrorHandler("Authentication required", 401));
      }

      if (!last_viewed) {
        return next(new ErrorHandler("Last viewed date is required", 400));
      }

      const lastViewedDate = new Date(last_viewed as string);
      if (isNaN(lastViewedDate.getTime())) {
        return next(new ErrorHandler("Invalid date format", 400));
      }

      const newUpdates = await productUpdateService.getNewUpdatesSince(
        productId,
        lastViewedDate,
      );

      res.status(200).json({
        success: true,
        data: newUpdates,
      });
    },
  ),

  // Bulk publish updates
  bulkPublishUpdates: catchAsyncError(
    async (req: AuthRequest, res: Response, next: NextFunction) => {
      const userId = req.user?.id;
      const { update_ids } = req.body;

      if (!userId) {
        return next(new ErrorHandler("Authentication required", 401));
      }

      if (!Array.isArray(update_ids) || update_ids.length === 0) {
        return next(new ErrorHandler("Update IDs array is required", 400));
      }

      try {
        const publishedUpdates = await productUpdateService.bulkPublishUpdates(
          update_ids,
          userId,
        );

        // Emit socket events for published updates
        if (req.app.get("io")) {
          const io = req.app.get("io");
          for (const update of publishedUpdates) {
            io.to(`product:${update.product_id}`).emit("update:published", {
              update,
              product_id: update.product_id,
            });
          }
        }

        res.status(200).json({
          success: true,
          message: `${publishedUpdates.length} updates published successfully`,
          data: publishedUpdates,
        });
      } catch (error: any) {
        return next(new ErrorHandler(error.message, 400));
      }
    },
  ),

  // Get update timeline
  getUpdateTimeline: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { productId } = req.params;

      const timeline = await productUpdateService.getUpdateTimeline(productId);

      res.status(200).json({
        success: true,
        data: timeline,
      });
    },
  ),

  // Search updates
  searchUpdates: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { q } = req.query;
      const { product_id, version_type, limit = 20 } = req.query;

      if (!q || q.toString().trim().length < 2) {
        return next(
          new ErrorHandler("Search query must be at least 2 characters", 400),
        );
      }

      const filters = {
        product_id: product_id as string,
        version_type: version_type as string,
        limit: parseInt(limit as string),
      };

      const results = await productUpdateService.searchUpdates(
        q.toString(),
        filters,
      );

      res.status(200).json({
        success: true,
        data: results,
      });
    },
  ),

  // Get user's updates
  getUserUpdates: catchAsyncError(
    async (req: AuthRequest, res: Response, next: NextFunction) => {
      const userId = req.user?.id;
      const { page = 1, limit = 20, product_id, status = "draft" } = req.query;

      if (!userId) {
        return next(new ErrorHandler("Authentication required", 401));
      }

      const filters: UpdateFilters = {
        page: parseInt(page as string),
        limit: parseInt(limit as string),
        created_by: userId,
        product_id: product_id as string,
        status: status as string,
        is_public: undefined, // Show all user's updates regardless of public status
      };

      const result = await productUpdateService.getAllUpdates(filters);

      res.status(200).json({
        success: true,
        ...result,
      });
    },
  ),

  // Compare two updates
  compareUpdates: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { updateId1, updateId2 } = req.params;

      const [update1, update2] = await Promise.all([
        productUpdateService.getUpdateById(updateId1, false),
        productUpdateService.getUpdateById(updateId2, false),
      ]);

      if (!update1 || !update2) {
        return next(new ErrorHandler("One or both updates not found", 404));
      }

      if (update1.product_id !== update2.product_id) {
        return next(
          new ErrorHandler("Updates must belong to the same product", 400),
        );
      }

      const comparison = {
        update1,
        update2,
        differences: {
          version: update1.version !== update2.version,
          version_type: update1.version_type !== update2.version_type,
          title: update1.title !== update2.title,
          release_date: update1.release_date !== update2.release_date,
          new_features_count:
            update1.new_features?.length !== update2.new_features?.length,
          improvements_count:
            update1.improvements?.length !== update2.improvements?.length,
          bug_fixes_count:
            update1.bug_fixes?.length !== update2.bug_fixes?.length,
          breaking_changes_count:
            update1.breaking_changes?.length !==
            update2.breaking_changes?.length,
        },
      };

      res.status(200).json({
        success: true,
        data: comparison,
      });
    },
  ),

  // Set update as latest
  setAsLatest: catchAsyncError(
    async (req: AuthRequest, res: Response, next: NextFunction) => {
      const { id } = req.params;
      const userId = req.user?.id;

      if (!userId) {
        return next(new ErrorHandler("Authentication required", 401));
      }

      try {
        const updatedUpdate = await productUpdateService.updateUpdate(
          id,
          { is_latest: true },
          userId,
        );

        res.status(200).json({
          success: true,
          message: "Update set as latest successfully",
          data: updatedUpdate,
        });
      } catch (error: any) {
        return next(
          new ErrorHandler(
            error.message,
            error.message.includes("permission") ? 403 : 400,
          ),
        );
      }
    },
  ),

  // Get update by version
  getUpdateByVersion: catchAsyncError(
    async (req: Request, res: Response, next: NextFunction) => {
      const { productId, version } = req.params;

      const query = `
      SELECT 
        u.*,
        product.name as product_name,
        product.slug as product_slug,
        creator.username as created_by_username
      FROM products_updates u
      LEFT JOIN products product ON u.product_id = product.id
      LEFT JOIN accounts creator ON u.created_by = creator.id
      WHERE u.product_id = $1 AND u.version = $2;
    `;

      const result = await database.query(query, [productId, version]);
      const update = result.rows[0];

      if (!update) {
        return next(new ErrorHandler("Update not found", 404));
      }

      if (!update.is_public && update.status !== "published") {
        const userId = (req as AuthRequest).user?.id;
        if (!userId || userId !== update.created_by) {
          return next(
            new ErrorHandler("Update is not publicly available", 403),
          );
        }
      }

      res.status(200).json({
        success: true,
        data: update,
      });
    },
  ),
};
