import { v4 as uuidv4 } from 'uuid';
import { db } from '../application/database';
import { ResponseError } from '../error/response-error';
import {
  FooterResponse,
  toFooterResponse,
  CreateFooterRequest,
  UpdateFooterRequest,
  FooterItemResponse,
  toFooterItemResponse,
  CreateFooterItemRequest,
  UpdateFooterItemRequest,
  UpdateFooterOrderRequest,
  Footer,
  FooterItem,
} from '../model/footer-model';
import { FooterValidation } from '../validation/footer-validation';
import { Validation } from '../validation/validation';

export class FooterService {
  static async create(request: CreateFooterRequest): Promise<FooterResponse> {
    // Validate the create request
    const createRequest = Validation.validate(FooterValidation.CREATE, request);

    // Generate a UUID for the new footer
    const footerId = uuidv4();

    // Insert the new footer into the database with the UUID
    await db.query('INSERT INTO footer (id, name, type, `order`) VALUES (?, ?, ?, ?)', [
      footerId,
      createRequest.name,
      createRequest.type,
      createRequest.order,
    ]);

    // Retrieve the newly created footer by the UUID
    const footer = await db.queryOne<Footer & { items: FooterItem[] }>('SELECT * FROM footer WHERE id = ? LIMIT 1', [
      footerId,
    ]);

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

    // Initialize the items array for the footer
    footer.items = [];

    return toFooterResponse(footer);
  }

  static async getAll(): Promise<FooterResponse[]> {
    // Query to get all footers ordered by 'order' field
    const footers = await db.query<Footer>('SELECT * FROM footer ORDER BY `order` ASC');

    // Query to get all footer items
    const footerItems = await db.query<FooterItem>('SELECT * FROM footer_item ORDER BY created_at ASC');

    // Map footer items to their respective footers
    const footersWithItems = footers.map((footer) => {
      const items = footerItems.filter((item) => item.footer_id === footer.id);
      return { ...footer, items };
    });

    // Map each footer to the response format
    return footersWithItems.map(toFooterResponse);
  }

  static async get(id: string): Promise<FooterResponse> {
    // Retrieve single footer by ID
    const footer = await db.queryOne<Footer & { items: FooterItem[] }>('SELECT * FROM footer WHERE id = ? LIMIT 1', [
      id,
    ]);

    if (!footer) {
      throw new ResponseError(404, 'Footer not found');
    }

    // Retrieve the items associated with the footer
    const footerItems = await db.query<FooterItem>(
      'SELECT * FROM footer_item WHERE footer_id = ? ORDER BY created_at ASC',
      [id]
    );

    // Add the items to the footer object
    footer.items = footerItems;

    return toFooterResponse(footer);
  }

  static async update(id: string, request: UpdateFooterRequest): Promise<FooterResponse> {
    // Check if the footer exists
    const existingFooter = await db.queryOne<{ id: string }>('SELECT id FROM footer WHERE id = ? LIMIT 1', [id]);

    if (!existingFooter) {
      throw new ResponseError(404, 'Footer not found');
    }

    // Validate the update request
    const updateRequest = Validation.validate(FooterValidation.UPDATE, request);

    // Update the footer in the database
    await db.query('UPDATE footer SET name = ? WHERE id = ?', [updateRequest.name, id]);

    // Retrieve the updated footer by the ID
    const footer = await db.queryOne<Footer & { items: FooterItem[] }>('SELECT * FROM footer WHERE id = ? LIMIT 1', [
      id,
    ]);

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

    // Retrieve the items associated with the updated footer
    const footerItems = await db.query<FooterItem>('SELECT * FROM footer_item WHERE footer_id = ?', [id]);

    // Add the items to the footer object
    footer.items = footerItems;

    return toFooterResponse(footer);
  }

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

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

    // Retrieve the updated footer by ID
    const footer = await db.queryOne<Footer & { items: FooterItem[] }>('SELECT * FROM footer WHERE id = ? LIMIT 1', [
      id,
    ]);

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

    // Retrieve the items associated with the updated footer
    const footerItems = await db.query<FooterItem>('SELECT * FROM footer_item WHERE footer_id = ?', [id]);

    // Add the items to the footer object
    footer.items = footerItems;

    return toFooterResponse(footer);
  }

  static async updateOrder(request: UpdateFooterOrderRequest[]): Promise<void> {
    // Validate the update order request
    const updateRequests = Validation.validate(FooterValidation.UPDATE_ORDER, request);

    // Start a transaction for batch processing
    await db.transaction(async (conn) => {
      for (const updateRequest of updateRequests) {
        // Check if the footer exists
        const [existingFooter] = await conn.query('SELECT id FROM footer WHERE id = ? LIMIT 1', [updateRequest.id]);

        if (!existingFooter) {
          throw new ResponseError(404, 'Footer not found');
        }

        // Update the footer in the database
        await conn.query('UPDATE footer SET `order` = ? WHERE id = ?', [updateRequest.order, updateRequest.id]);
      }
    });
  }

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

    if (!existingFooter) {
      throw new ResponseError(404, 'Footer not found');
    }

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

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

    if (!existingFooter) {
      throw new ResponseError(404, 'Footer not found');
    }
  }

  static async createItem(footerId: string, request: CreateFooterItemRequest): Promise<FooterItemResponse> {
    // Check if the footer exists
    await this.#checkFooterExist(footerId);

    // Validate the create request
    const createRequest = Validation.validate(FooterValidation.CREATE_ITEM, request);

    // Generate a UUID for the new footer item
    const itemId = uuidv4();

    // Insert the new footer item into the database with the UUID
    await db.query('INSERT INTO footer_item (id, footer_id, name, url) VALUES (?, ?, ?, ?)', [
      itemId,
      footerId,
      createRequest.name,
      createRequest.url,
    ]);

    // Retrieve the newly created footer item by the UUID
    const footerItem = await db.queryOne<FooterItem>('SELECT * FROM footer_item WHERE id = ? LIMIT 1', [itemId]);

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

    return toFooterItemResponse(footerItem);
  }

  static async updateItem(
    footerId: string,
    itemId: string,
    request: UpdateFooterItemRequest
  ): Promise<FooterItemResponse> {
    // Check if the footer exists
    await this.#checkFooterExist(footerId);

    // Check if the footer item exists
    await this.#checkFooterItemExist(footerId, itemId);

    // Validate the update request
    const updateRequest = Validation.validate(FooterValidation.UPDATE_ITEM, request);

    // Update the footer item in the database
    await db.query('UPDATE footer_item SET name = ?, url = ? WHERE id = ?', [
      updateRequest.name,
      updateRequest.url,
      itemId,
    ]);

    // Retrieve the updated footer item by the ID
    const footerItem = await db.queryOne<FooterItem>('SELECT * FROM footer_item WHERE id = ? LIMIT 1', [itemId]);

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

    return toFooterItemResponse(footerItem);
  }

  static async updateItemVisibility(footerId: string, itemId: string, visible: boolean): Promise<FooterItemResponse> {
    // Check if the footer exists
    await this.#checkFooterExist(footerId);

    // Check if the footer item exists
    await this.#checkFooterItemExist(footerId, itemId);

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

    // Retrieve the updated footer item by the ID
    const footerItem = await db.queryOne<FooterItem>('SELECT * FROM footer_item WHERE id = ? LIMIT 1', [itemId]);

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

    return toFooterItemResponse(footerItem);
  }

  static async deleteItem(footerId: string, itemId: string): Promise<void> {
    // Check if the footer exists
    await this.#checkFooterExist(footerId);

    // Check if the footer item exists
    await this.#checkFooterItemExist(footerId, itemId);

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

  static async #checkFooterItemExist(footerId: string, itemId: string): Promise<void> {
    // Check if the footer item exists
    const existingFooterItem = await db.queryOne<{ id: string }>(
      'SELECT id FROM footer_item WHERE id = ? AND footer_id = ? LIMIT 1',
      [itemId, footerId]
    );

    if (!existingFooterItem) {
      throw new ResponseError(404, 'Footer item not found');
    }
  }
}
