import fs from 'fs';
import path from 'path';

import { v4 as uuidv4 } from 'uuid';
import { db } from '../application/database';
import { ResponseError } from '../error/response-error';
import {
  ContentResponse,
  toContentResponse,
  CreateContentRequest,
  UpdateContentRequest,
  UpdateContentValueRequest,
  CreateColorRequest,
  ColorResponse,
  UpdateColorRequest,
  Content,
  toColorResponse,
  Color,
} from '../model/content-model';
import { ContentValidation } from '../validation/content-validation';
import { Validation } from '../validation/validation';

export class ContentService {
  static async create(request: CreateContentRequest): Promise<ContentResponse> {
    // Validate the create request
    const createRequest = Validation.validate(ContentValidation.CREATE, request);

    // Generate a UUID for the new content
    const contentId = uuidv4();

    // Insert the new content into the database with the UUID
    await db.query('INSERT INTO content (id, page, section, type, key, value) VALUES (?, ?, ?, ?, ?, ?, ?)', [
      contentId,
      createRequest.page,
      createRequest.section,
      createRequest.type,
      createRequest.key,
      JSON.stringify(createRequest.value),
    ]);

    // Retrieve the newly created content by the UUID
    const content = await db.queryOne<Content>('SELECT * FROM content WHERE id = ? LIMIT 1', [contentId]);

    if (!content) {
      throw new ResponseError(500, 'Failed to create content');
    }

    return toContentResponse(content);
  }

  static async update(id: string, request: UpdateContentRequest): Promise<ContentResponse> {
    // Validate the update request
    const updateRequest = Validation.validate(ContentValidation.UPDATE, request);

    // Check if the content exists
    await this.#checkContentExist(id);

    // Update the content in the database
    await db.query('UPDATE content SET page = ?, section = ?, type = ?, key = ?, value = ? WHERE id = ?', [
      updateRequest.page,
      updateRequest.section,
      updateRequest.type,
      updateRequest.key,
      JSON.stringify(updateRequest.value),
      id,
    ]);

    // Retrieve the updated content by the UUID
    const content = await db.queryOne<Content>('SELECT * FROM content WHERE id = ? LIMIT 1', [id]);

    if (!content) {
      throw new ResponseError(500, 'Failed to update content');
    }

    return toContentResponse(content);
  }

  static async updateValue(
    id: string,
    request: UpdateContentValueRequest,
    file?: Express.Multer.File
  ): Promise<ContentResponse> {
    // Validate the update value request
    const updateValueRequest = Validation.validate(ContentValidation.UPDATE_VALUE, request);

    // Get the value from the request
    const { value } = updateValueRequest;

    // Check if the content exists
    const currentContent = await this.#checkContentExist(id);

    // If the request contains a file, update the image path
    let updatedValue: any = value;
    let imagePath: string | undefined;

    if (file) {
      imagePath = `/public/content/${file.filename}`;
      const oldImagePath =
        currentContent.type === 'IMAGE'
          ? currentContent.value.src
          : currentContent.type === 'BENEFIT_CARD'
          ? currentContent.value.imageSrc
          : null;

      // Delete the old image if it exists
      if (oldImagePath) {
        const fullOldImagePath = path.join(__dirname, '..', '..', oldImagePath);
        if (fs.existsSync(fullOldImagePath)) {
          fs.unlink(fullOldImagePath, (err) => {
            if (err) {
              console.error('Error deleting the old image:', err);
            } else {
              console.log('Old image successfully deleted:', oldImagePath);
            }
          });
        }
      }
    }

    // Update the value based on the content type
    if (currentContent.type === 'IMAGE') {
      updatedValue = {
        src: imagePath || currentContent.value.src,
      };
    } else if (currentContent.type === 'BENEFIT_CARD') {
      updatedValue = {
        imageSrc: imagePath || currentContent.value.imageSrc,
        title: updatedValue.title,
        description: updatedValue.description,
      };
    }

    // Update the content in the database
    await db.query('UPDATE content SET value = ? WHERE id = ?', [JSON.stringify(updatedValue), id]);

    // Retrieve the updated content by ID
    const content = await db.queryOne<Content>('SELECT * FROM content WHERE id = ?', [id]);

    if (!content) {
      throw new ResponseError(500, 'Failed to update content value');
    }

    return toContentResponse(content);
  }

  static async updateVisibility(id: string, visible: boolean): Promise<ContentResponse> {
    // Check if the content exists
    await this.#checkContentExist(id);

    // Update the visibility of the content in the database
    await db.query('UPDATE content SET visible = ? WHERE id = ?', [visible, id]);

    // Retrieve the updated content by ID
    const content = await db.queryOne<Content>('SELECT * FROM content WHERE id = ?', [id]);

    if (!content) {
      throw new ResponseError(500, 'Failed to update content visibility');
    }

    return toContentResponse(content);
  }

  static async delete(id: string): Promise<ContentResponse> {
    // Retrieve the content before deleting it
    const content = await this.#checkContentExist(id);

    // Delete the content from the database
    await db.query('DELETE FROM content WHERE id = ?', [id]);

    return toContentResponse(content);
  }

  static async getAll(): Promise<ContentResponse[]> {
    // Retrieve all content from the database
    const contents = await db.query<Content>('SELECT * FROM content ORDER BY created_at ASC');

    // Map each content to the response format
    return contents.map(toContentResponse);
  }

  static async getAllByPage(page: string): Promise<ContentResponse[]> {
    // Retrieve all content from the database by page
    const contents = await db.query<Content>('SELECT * FROM content WHERE page = ? ORDER BY created_at ASC', [page]);
    
    // Map each content to the response format
    return contents.map(toContentResponse);
  }

  static async getAllBySection(section: string): Promise<ContentResponse[]> {
    // Retrieve all content from the database by section
    const contents = await db.query<Content>('SELECT * FROM content WHERE section = ? ORDER BY created_at ASC', [
      section,
    ]);

    // Map each content to the response format
    return contents.map(toContentResponse);
  }

  static async get(id: string): Promise<ContentResponse> {
    // Retrieve single content by ID
    const content = await db.queryOne<Content>('SELECT * FROM content WHERE id = ? LIMIT 1', [id]);

    if (!content) {
      throw new ResponseError(404, 'Content not found');
    }

    return toContentResponse(content);
  }

  static async #checkContentExist(id: string): Promise<ContentResponse> {
    // Check if the content exists
    const content = await db.queryOne<Content>('SELECT * FROM content WHERE id = ? LIMIT 1', [id]);

    if (!content) {
      throw new ResponseError(404, 'Content not found');
    }

    return content;
  }

  static async createColor(request: CreateColorRequest): Promise<ColorResponse> {
    // Validate the create request
    const createRequest = Validation.validate(ContentValidation.CREATE_COLOR, request);

    // Generate a UUID for the new color
    const colorId = uuidv4();

    // Insert the new color into the database with the UUID
    await db.query('INSERT INTO color (id, hex) VALUES (?, ?)', [colorId, createRequest.hex]);

    // Retrieve the newly created color by the UUID
    const color = await db.queryOne<Color>('SELECT * FROM color WHERE id = ? LIMIT 1', [colorId]);

    if (!color) {
      throw new ResponseError(500, 'Failed to create color');
    }

    return color;
  }

  static async updateColors(request: UpdateColorRequest[]): Promise<void> {
    // Validate the update request
    const updateRequest = Validation.validate(ContentValidation.UPDATE_COLORS, request);

    // Start a transaction for batch processing
    await db.transaction(async (conn) => {
      for (const { id, hex } of updateRequest) {
        // Update the color in the database
        await conn.query('UPDATE color SET hex = ? WHERE id = ?', [hex, id]);
      }
    });
  }

  static async deleteColor(id: string): Promise<void> {
    // Check if the color exists
    const existingColor = await db.queryOne<{ id: string }>('SELECT id FROM color WHERE id = ? LIMIT 1', [id]);

    if (!existingColor) {
      throw new ResponseError(404, 'Color not found');
    }

    // Delete the color from the database
    await db.query('DELETE FROM color WHERE id = ?', [id]);
  }

  static async getAllColors(): Promise<ColorResponse[]> {
    // Retrieve all colors from the database
    const colors = await db.query<Color>('SELECT * FROM color ORDER BY created_at ASC');

    // Map each color to the response
    return colors.map(toColorResponse);
  }

  static async getColor(id: string): Promise<ColorResponse> {
    // Retrieve a single color by ID
    const color = await db.queryOne<Color>('SELECT * FROM color WHERE id = ? LIMIT 1', [id]);

    if (!color) {
      throw new ResponseError(404, 'Color not found');
    }

    return color;
  }
}
