import type { Context } from "koa";
import { ObjectId } from "mongodb";
import dbConfig from "../config/database";
import type { Category } from "../models";
import DatabaseService from "../services/database";

const db = DatabaseService.getInstance();

// Middleware to check if user is admin
function requireAdmin(ctx: Context, next: Function) {
  const user = (ctx.state as any).user;
  if (user.role !== "admin") {
    ctx.status = 403;
    ctx.body = {
      success: false,
      message: "Admin access required",
    };
    return;
  }
  return next();
}

export default {
  // Get categories for a user
  async getUserCategories(ctx: Context) {
    const user = (ctx.state as any).user;
    const userId = user.id;

    try {
      const categoriesCollection = db.getCollection<Category>(
        dbConfig.collections.categories,
      );

      const categories = await categoriesCollection
        .find({
          userId: new ObjectId(String(userId)),
        })
        .toArray();

      ctx.body = {
        success: true,
        data: categories,
      };
    } catch (error) {
      ctx.status = 500;
      ctx.body = {
        success: false,
        message: "Error retrieving categories",
        error: (error as Error).message,
      };
    }
  },

  // Add new category
  async addCategory(ctx: Context) {
    const user = (ctx.state as any).user;
    const userId = user.id;
    const { name, icon, color } = ctx.request.body as {
      name: string;
      icon?: string;
      color?: string;
    };

    if (!name) {
      ctx.status = 400;
      ctx.body = {
        success: false,
        message: "Category name is required",
      };
      return;
    }

    try {
      const categoriesCollection = db.getCollection<Category>(
        dbConfig.collections.categories,
      );

      // Check if category already exists
      const existingCategory = await categoriesCollection.findOne({
        userId: new ObjectId(userId),
        name,
      });

      if (existingCategory) {
        ctx.status = 409;
        ctx.body = {
          success: false,
          message: "Category already exists",
        };
        return;
      }

      const now = new Date();
      const newCategory: Category = {
        userId: new ObjectId(userId),
        name,
        icon,
        color,
        createdAt: now,
        updatedAt: now,
      };

      const result = await categoriesCollection.insertOne(newCategory);

      ctx.status = 201;
      ctx.body = {
        success: true,
        message: "Category added successfully",
        categoryId: result.insertedId,
      };
    } catch (error) {
      ctx.status = 500;
      ctx.body = {
        success: false,
        message: "Error adding category",
        error: (error as Error).message,
      };
    }
  },

  // Update category
  async updateCategory(ctx: Context) {
    const user = (ctx.state as any).user;
    const userId = user.id;
    const categoryId = ctx.params.id;

    const { name, icon, color } = ctx.request.body as {
      name?: string;
      icon?: string;
      color?: string;
    };

    if (!categoryId || !ObjectId.isValid(categoryId)) {
      ctx.status = 400;
      ctx.body = {
        success: false,
        message: "Valid category ID is required",
      };
      return;
    }

    try {
      const categoriesCollection = db.getCollection<Category>(
        dbConfig.collections.categories,
      );

      // Make sure category belongs to user
      const category = await categoriesCollection.findOne({
        _id: new ObjectId(categoryId),
        userId: new ObjectId(userId),
      });

      if (!category) {
        ctx.status = 404;
        ctx.body = {
          success: false,
          message: "Category not found",
        };
        return;
      }

      const updateData: Partial<Category> = {
        updatedAt: new Date(),
      };

      if (name) updateData.name = name;
      if (icon) updateData.icon = icon;
      if (color) updateData.color = color;

      await categoriesCollection.updateOne(
        { _id: new ObjectId(categoryId) },
        { $set: updateData },
      );

      ctx.body = {
        success: true,
        message: "Category updated successfully",
      };
    } catch (error) {
      ctx.status = 500;
      ctx.body = {
        success: false,
        message: "Error updating category",
        error: (error as Error).message,
      };
    }
  },

  // Delete category (Admin only)
  async deleteCategory(ctx: Context) {
    const user = (ctx.state as any).user;
    const categoryId = ctx.params.id;

    if (!categoryId || !ObjectId.isValid(categoryId)) {
      ctx.status = 400;
      ctx.body = {
        success: false,
        message: "Valid category ID is required",
      };
      return;
    }

    try {
      const categoriesCollection = db.getCollection<Category>(
        dbConfig.collections.categories,
      );

      // Check if category exists
      const category = await categoriesCollection.findOne({
        _id: new ObjectId(categoryId),
      });

      if (!category) {
        ctx.status = 404;
        ctx.body = {
          success: false,
          message: "Category not found",
        };
        return;
      }

      // Check if category is being used in transactions
      const transactionsCollection = db.getCollection(
        dbConfig.collections.transactions,
      );

      const transactionCount = await transactionsCollection.countDocuments({
        categoryId: new ObjectId(categoryId),
      });

      if (transactionCount > 0) {
        ctx.status = 409;
        ctx.body = {
          success: false,
          message: `Cannot delete category. It is used in ${transactionCount} transaction(s).`,
        };
        return;
      }

      await categoriesCollection.deleteOne({
        _id: new ObjectId(categoryId),
      });

      ctx.body = {
        success: true,
        message: "Category deleted successfully",
      };
    } catch (error) {
      ctx.status = 500;
      ctx.body = {
        success: false,
        message: "Error deleting category",
        error: (error as Error).message,
      };
    }
  },

  // Get all categories (Admin only)
  async getAllCategories(ctx: Context) {
    try {
      const categoriesCollection = db.getCollection<Category>(
        dbConfig.collections.categories,
      );

      const categories = await categoriesCollection
        .find({})
        .sort({ createdAt: -1 })
        .toArray();

      ctx.body = {
        success: true,
        data: categories,
      };
    } catch (error) {
      ctx.status = 500;
      ctx.body = {
        success: false,
        message: "Error retrieving categories",
        error: (error as Error).message,
      };
    }
  },

  // Create global category (Admin only)
  async createGlobalCategory(ctx: Context) {
    const { name, icon, color } = ctx.request.body as {
      name: string;
      icon?: string;
      color?: string;
    };

    if (!name) {
      ctx.status = 400;
      ctx.body = {
        success: false,
        message: "Category name is required",
      };
      return;
    }

    try {
      const categoriesCollection = db.getCollection<Category>(
        dbConfig.collections.categories,
      );

      // Check if global category already exists
      const existingCategory = await categoriesCollection.findOne({
        name,
        userId: null, // Global categories have no userId
      });

      if (existingCategory) {
        ctx.status = 409;
        ctx.body = {
          success: false,
          message: "Global category already exists",
        };
        return;
      }

      const now = new Date();
      const newCategory: any = {
        userId: null, // Global category
        name,
        icon,
        color,
        createdAt: now,
        updatedAt: now,
      };

      const result = await categoriesCollection.insertOne(newCategory);

      ctx.status = 201;
      ctx.body = {
        success: true,
        message: "Global category created successfully",
        categoryId: result.insertedId,
      };
    } catch (error) {
      ctx.status = 500;
      ctx.body = {
        success: false,
        message: "Error creating global category",
        error: (error as Error).message,
      };
    }
  },

  // Update any category (Admin only)
  async updateAnyCategory(ctx: Context) {
    const categoryId = ctx.params.id;
    const { name, icon, color } = ctx.request.body as {
      name?: string;
      icon?: string;
      color?: string;
    };

    if (!categoryId || !ObjectId.isValid(categoryId)) {
      ctx.status = 400;
      ctx.body = {
        success: false,
        message: "Valid category ID is required",
      };
      return;
    }

    try {
      const categoriesCollection = db.getCollection<Category>(
        dbConfig.collections.categories,
      );

      const category = await categoriesCollection.findOne({
        _id: new ObjectId(categoryId),
      });

      if (!category) {
        ctx.status = 404;
        ctx.body = {
          success: false,
          message: "Category not found",
        };
        return;
      }

      const updateData: Partial<Category> = {
        updatedAt: new Date(),
      };

      if (name) updateData.name = name;
      if (icon) updateData.icon = icon;
      if (color) updateData.color = color;

      await categoriesCollection.updateOne(
        { _id: new ObjectId(categoryId) },
        { $set: updateData },
      );

      ctx.body = {
        success: true,
        message: "Category updated successfully",
      };
    } catch (error) {
      ctx.status = 500;
      ctx.body = {
        success: false,
        message: "Error updating category",
        error: (error as Error).message,
      };
    }
  },

  // Middleware function for admin routes
  requireAdmin,
};
