import { desc, eq, type Database } from "@pkg/db";
import { product } from "@pkg/db/schema";
import { getError, Logger } from "@pkg/logger";
import { ERROR_CODES, type Result } from "@pkg/result";
import { nanoid } from "nanoid";
import {
  productModel,
  type CreateProductPayload,
  type ProductModel,
  type UpdateProductPayload,
} from "./data";

export class ProductRepository {
  private db: Database;

  constructor(db: Database) {
    this.db = db;
  }

  async listAllProducts(): Promise<Result<ProductModel[]>> {
    try {
      const results = await this.db.query.product.findMany({
        orderBy: [desc(product.createdAt)],
      });
      const out = [] as ProductModel[];
      for (const result of results) {
        const parsed = productModel.safeParse(result);
        if (!parsed.success) {
          Logger.error("Failed to parse product");
          Logger.error(parsed.error);
          continue;
        }
        out.push(parsed.data);
      }
      return { data: out };
    } catch (e) {
      return {
        error: getError(
          {
            code: ERROR_CODES.DATABASE_ERROR,
            message: "Failed to fetch products",
            detail:
              "An error occurred while retrieving products from the database",
            userHint: "Please try refreshing the page",
            actionable: false,
          },
          e,
        ),
      };
    }
  }

  async getProductById(id: number | string): Promise<Result<ProductModel>> {
    try {
      const result = await this.db.query.product.findFirst({
        where:
          typeof id === "number" ? eq(product.id, id) : eq(product.linkId, id),
      });

      if (!result) {
        return {
          error: getError({
            code: ERROR_CODES.NOT_FOUND_ERROR,
            message: "Product not found",
            detail: "No product exists with the provided ID",
            userHint: "Please check the product ID and try again",
            actionable: true,
          }),
        };
      }

      const parsed = productModel.safeParse(result);
      if (!parsed.success) {
        Logger.error("Failed to parse product", result);
        return {
          error: getError({
            code: ERROR_CODES.INTERNAL_SERVER_ERROR,
            message: "Failed to parse product",
            userHint: "Please try again",
            detail: "Failed to parse product",
          }),
        };
      }
      return { data: parsed.data };
    } catch (e) {
      return {
        error: getError(
          {
            code: ERROR_CODES.DATABASE_ERROR,
            message: "Failed to fetch product",
            detail:
              "An error occurred while retrieving the product from the database",
            userHint: "Please try refreshing the page",
            actionable: false,
          },
          e,
        ),
      };
    }
  }

  async createProduct(payload: CreateProductPayload): Promise<Result<number>> {
    try {
      // Generate a unique linkId
      const linkId = nanoid(16);

      const result = await this.db
        .insert(product)
        .values({
          linkId,
          title: payload.title,
          description: payload.description,
          longDescription: payload.longDescription,
          price: payload.price.toString(),
          discountPrice: payload.discountPrice.toString(),
        })
        .returning({ id: product.id })
        .execute();

      if (!result || result.length === 0) {
        throw new Error("Failed to create product record");
      }

      return { data: result[0].id };
    } catch (e) {
      return {
        error: getError(
          {
            code: ERROR_CODES.DATABASE_ERROR,
            message: "Failed to create product",
            detail: "An error occurred while creating the product",
            userHint: "Please try again",
            actionable: false,
          },
          e,
        ),
      };
    }
  }

  async updateProduct(payload: UpdateProductPayload): Promise<Result<boolean>> {
    try {
      if (!payload.id) {
        return {
          error: getError({
            code: ERROR_CODES.VALIDATION_ERROR,
            message: "Invalid product ID",
            detail: "No product ID was provided for the update operation",
            userHint: "Please provide a valid product ID",
            actionable: true,
          }),
        };
      }

      // Check if product exists
      const existing = await this.db.query.product.findFirst({
        where: eq(product.id, payload.id),
      });

      if (!existing) {
        return {
          error: getError({
            code: ERROR_CODES.NOT_FOUND_ERROR,
            message: "Product not found",
            detail: "No product exists with the provided ID",
            userHint: "Please check the product ID and try again",
            actionable: true,
          }),
        };
      }

      // Build the update object with only the fields that are provided
      const updateValues: Record<string, any> = {};

      if (payload.title !== undefined) updateValues.title = payload.title;
      if (payload.description !== undefined)
        updateValues.description = payload.description;
      if (payload.longDescription !== undefined)
        updateValues.longDescription = payload.longDescription;
      if (payload.price !== undefined)
        updateValues.price = payload.price.toString();
      if (payload.discountPrice !== undefined)
        updateValues.discountPrice = payload.discountPrice.toString();
      updateValues.updatedAt = new Date();

      await this.db
        .update(product)
        .set(updateValues)
        .where(eq(product.id, payload.id))
        .execute();

      return { data: true };
    } catch (e) {
      return {
        error: getError(
          {
            code: ERROR_CODES.DATABASE_ERROR,
            message: "Failed to update product",
            detail: "An error occurred while updating the product",
            userHint: "Please try again",
            actionable: false,
          },
          e,
        ),
      };
    }
  }

  async deleteProduct(id: number): Promise<Result<boolean>> {
    try {
      // Check if product exists
      const existing = await this.db.query.product.findFirst({
        where: eq(product.id, id),
      });

      if (!existing) {
        return {
          error: getError({
            code: ERROR_CODES.NOT_FOUND_ERROR,
            message: "Product not found",
            detail: "No product exists with the provided ID",
            userHint: "Please check the product ID and try again",
            actionable: true,
          }),
        };
      }

      await this.db.delete(product).where(eq(product.id, id)).execute();

      return { data: true };
    } catch (e) {
      return {
        error: getError(
          {
            code: ERROR_CODES.DATABASE_ERROR,
            message: "Failed to delete product",
            detail: "An error occurred while deleting the product",
            userHint: "Please try again",
            actionable: false,
          },
          e,
        ),
      };
    }
  }

  async refreshProductLinkId(id: number): Promise<Result<string>> {
    try {
      // Check if product exists
      const existing = await this.db.query.product.findFirst({
        where: eq(product.id, id),
      });

      if (!existing) {
        return {
          error: getError({
            code: ERROR_CODES.NOT_FOUND_ERROR,
            message: "Product not found",
            detail: "No product exists with the provided ID",
            userHint: "Please check the product ID and try again",
            actionable: true,
          }),
        };
      }

      // Generate a new unique linkId
      const newLinkId = nanoid(16);

      await this.db
        .update(product)
        .set({
          linkId: newLinkId,
          updatedAt: new Date(),
        })
        .where(eq(product.id, id))
        .execute();

      return { data: newLinkId };
    } catch (e) {
      return {
        error: getError(
          {
            code: ERROR_CODES.DATABASE_ERROR,
            message: "Failed to refresh product link ID",
            detail: "An error occurred while refreshing the product's link ID",
            userHint: "Please try again",
            actionable: false,
          },
          e,
        ),
      };
    }
  }
}
