/home/bdqbpbxa/dev-subdomains/admin.pixory.goodface.com.ua/src/api/project/services/project.ts
import { Core } from "@strapi/strapi";
import { ApplicationError } from '@strapi/utils/dist/errors';
import { round } from "src/api/utils/math";
import { Pricing } from "src/api/pricing/common/types/pricing";
import { getDiscountMultiplier } from "src/api/pricing/common/utils/discount-multiplier";
import { BookSettings } from "src/api/book-setting/common/types/book-settings";
import { BookType } from "src/api/book-type/common/types/book-type";
import { ShippingPrice } from "src/api/shipping-price/common/types/shipping-price";
import { PrintboxService } from "src/services/printbox/printbox.service";
import { PrintboxProject, ProductResponse, Project } from "../common/types/project";

export class ProjectService {
  constructor(private readonly strapi: Core.Strapi) {}

  private readonly bookTypeSignificanceLevels: {
    hardcover: 0,
    softcover: 1,
    layFlat: 2
  }

  async update(id: number, data: Partial<Project>): Promise<ProductResponse> {
    const updatedProject = await this.strapi.db.query('api::project.project').update({
      where: { id },
      data,
      populate: {
        product: true,
        bookSize: true,
        bookType: true,
        paperFinish: true,
      },
    });

    return this.transform(updatedProject);
  }

  async transform(project: Project): Promise<ProductResponse> {
    const bookType = await this.strapi.db.query('api::book-type.book-type').findOne() as BookType;

    const projectData = await this.getProjectData(project.printboxProjectUuid);
    const pageCount = projectData.params[0]?.page_count || 0;
    const defaultPages = bookType.minPages;
    const additionalPages = pageCount - bookType.minPages;

    const pricing = await this.getBookPricing(project);
    if (!pricing) {
      throw new ApplicationError(`Pricing not found for project type, size and paper finish. Project ID: ${project.id}`);
    }

    const defaultPagesPrice = await this.calculatePagesPrice(pricing, bookType.minPages);
    const additionalPagesPrice = await this.calculatePagesPrice(pricing, additionalPages);

    const discountedDefaultPagesPrice = pricing.discount ? defaultPagesPrice * getDiscountMultiplier(pricing.discount) : null;
    const discountedAdditionalPagesPrice = pricing.discount ? additionalPagesPrice * getDiscountMultiplier(pricing.discount) : null;

    const subtotal = defaultPagesPrice + additionalPagesPrice;
    const subtotalDiscounted = pricing.discount ? discountedAdditionalPagesPrice + discountedDefaultPagesPrice : null;
    const discount = subtotalDiscounted ? (subtotal - subtotalDiscounted) / subtotal * 100 : 0;

    return {
      ...project,
      
      defaultPages,
      additionalPages,

      defaultPagesPrice: round(defaultPagesPrice),
      additionalPagesPrice: round(additionalPagesPrice),

      discountedDefaultPagesPrice: discountedDefaultPagesPrice ? round(discountedDefaultPagesPrice) : null,
      discountedAdditionalPagesPrice: discountedAdditionalPagesPrice ? round(discountedAdditionalPagesPrice) : null,
      
      subtotal: round(subtotal),
      subtotalDiscounted: subtotalDiscounted ? round(subtotalDiscounted) : null,
      discount: round(discount),
    };
  }

  async calculatePagesPrice (pricing: Pricing, payablePages: number) {
    const projectPrice = pricing.base + (pricing.perPage * payablePages);
    return projectPrice;
  };

  async getBookPricing (project: Project): Promise<Pricing> {
    const bookSettings = await strapi.db.query('api::book-setting.book-setting').findOne({
      where: {
        bookType: project.bookType.id,
        bookSize: project.bookSize.id,
        paperFinish: project.paperFinish.id,
      },
      populate: {
        pricing: true,
      },
    }) as BookSettings;

    return bookSettings?.pricing;
  }

  async getHighestPageCount(projects: Project[]): Promise<number> {
    const printboxProjects = await this.getProjectsData(projects.map(p => p.printboxProjectUuid));

    return printboxProjects.reduce((max, projectData) => {
      return Math.max(max, projectData.params[0]?.page_count || 0);
    }, 0);
  }

  async getSignificantBookType(projects: Project[], highestPageCount: number): Promise<BookType> {
    const shippingPrices = await this.fetchShippingPrices(projects, highestPageCount);
    const filteredRegions = this.filterRegionsByBookCount(shippingPrices, projects.length);
    const bookTypes = this.extractBookTypes(filteredRegions);
    
    return this.getMostSignificantBookType(bookTypes);
  }

  private async fetchShippingPrices(projects: Project[], highestPageCount: number): Promise<ShippingPrice[]> {
    return await this.strapi.db.query('api::shipping-price.shipping-price').findMany({
      where: {
        minPage: { $lte: highestPageCount },
        maxPage: { $gte: highestPageCount },
        bookType: { $in: projects.map(p => p.bookType.id) }
      },
      populate: {
        shippingPrices: true,
        bookType: true,
      },
    }) as ShippingPrice[];
  }

  private filterRegionsByBookCount(regions: ShippingPrice[], projectCount: number): ShippingPrice[] {
    const exactMatchRegions = regions.filter(r => r.bookNumber === projectCount);
    
    if (exactMatchRegions.length > 0) {
      return exactMatchRegions;
    }
    
    const maxBookNumber = Math.max(...regions.map(r => r.bookNumber));
    return regions.filter(r => r.bookNumber === maxBookNumber);
  }

  private extractBookTypes(regions: ShippingPrice[]): BookType[] {
    return regions.map(r => r.bookType);
  }

  private getMostSignificantBookType(bookTypes: BookType[]): BookType {
    const sortedBookTypes = bookTypes.sort((a, b) => {
      return this.bookTypeSignificanceLevels[a.title] - this.bookTypeSignificanceLevels[b.title];
    });
    
    return sortedBookTypes[0];
  }

  async getProjectData(projectId: string): Promise<PrintboxProject> {
    const printboxService = new PrintboxService();
    return await printboxService.getProject(projectId);
  }

  async getProjectsData(projectId: string[]): Promise<PrintboxProject[]> {
    const projectsData = projectId.map(async (id) => await this.getProjectData(id));
    return Promise.all(projectsData);
  }
}