import * as XLSX from 'xlsx';
import * as XLSXStyle from 'xlsx-js-style';
import ExcelJS from 'exceljs';

export const NUMBER_FORMATS = {
  fullNumber: '#,##0.00',
  shortNumber: '#,##0',
  xNumber: '0.00"x"',
  xNumber1DP: '0.0"x"',
  thousands: '0.0,',
  percent: '0.00%',
  percent1DP: '0.0%',
  percent0DP: '0%',
  currencyFull$: '$#,##0.00',
  currencyShort$: '$#,##0',
};

export interface IExcelDataCellWithStyles {
  v?: string | number | Date | boolean;
  t?: string;
  s?: Object;
}

export interface IExcelJsCell {
  v?: ExcelJS.CellValue;
  t?: string;
  z?: string;
  hyperlink?: string;
  // s?: {
  //   font?: Partial<ExcelJS.Font>;
  //   alignment?: Partial<ExcelJS.Alignment>;
  //   fill?: Partial<ExcelJS.FillPattern>;
  //   border?: Partial<ExcelJS.Borders>;
  // };
  s?: Partial<ExcelJS.Style>;
}

export const cellStyles = {
  title: {
    font: { bold: true },
    border: {
      bottom: { style: 'medium' as ExcelJS.BorderStyle, color: { argb: 'FF000000' } },
    },
  },
};

const defaultColumnWidth = 20;

export const convertDataToFile = async (dataToConvert: { [sheetName: string]: any[] }[]) => {
  const wb = XLSX.utils.book_new();
  wb.Props = {
    Title: 'data',
    Subject: 'data',
    Author: 'GemCap',
    CreatedDate: new Date(),
  };
  XLSX.utils.sheet_add_aoa(wb, []);
  const sheetNames = [];

  const reduceSheetName = (str: string, maxLength = 28) => {
    if (str.length > maxLength) {
      return str.substring(0, maxLength) + '...';
    } else {
      return str;
    }
  };

  dataToConvert.forEach((sheet, index) => {
    Object.entries(sheet).forEach(([sheetName, data]) => {
      const sheetNameToSave = reduceSheetName(sheetNames.includes(sheetName) ? `${sheetName}_${index}` : sheetName);
      const ws = XLSX.utils.json_to_sheet(data, { skipHeader: true });
      const uniqueLetters = new Set<string>();
      Object.keys(ws).forEach((columnKey) => {
        const letter = columnKey.replace(/[0-9]/g, '');
        uniqueLetters.add(letter);
      });
      const uniqueLettersArray = Array.from(uniqueLetters);
      ws['!cols'] = uniqueLettersArray.map(() => ({ wch: defaultColumnWidth }));
      XLSX.utils.book_append_sheet(wb, ws, sheetNameToSave);
      sheetNames.push(sheetNameToSave);
    });
  });
  return XLSX.write(wb, { bookType: 'xlsx', type: 'buffer' });
};

const checkData = (dataToConvert: { [sheetName: string]: IExcelDataCellWithStyles[][] | IExcelJsCell[][] }) => {
  for (const [sheetName, data] of Object.entries(dataToConvert)) {
    data.forEach((row, rowIndex) => {
      row.forEach((cell, colIndex) => {
        const pos = `${sheetName} [${rowIndex + 1}, ${colIndex + 1}]`;

        if (cell.t === 'n') {
          if (cell.v === null) {
            console.warn(`❌ cell.t === 'n' but v === null at ${pos}:`, cell);
          } else if (cell.v !== undefined && typeof cell.v !== 'number') {
            console.warn(`❌ cell.t === 'n' but v is not a number at ${pos}:`, cell);
          }
        }

        if (cell.v === Infinity || (typeof cell.v === 'number' && !isFinite(cell.v))) {
          console.warn(`❌ Non-finite number at ${pos}:`, cell);
        }

        if (cell.t === 'd' && !(cell.v instanceof Date) && typeof cell.v !== 'number') {
          console.warn(`❌ cell.t === 'd' but v is not Date or number at ${pos}:`, cell);
        }

        if (cell.t === 's' && typeof cell.v !== 'string') {
          console.warn(`❌ cell.t === 's' but v is not string at ${pos}:`, cell);
        }

        if (cell.t === 'b' && typeof cell.v !== 'boolean') {
          console.warn(`❌ cell.t === 'b' but v is not boolean at ${pos}:`, cell);
        }

        if (cell.v === null && cell.z) {
          console.warn(`❌ Null value with format (z) at ${pos}:`, cell);
        }

        if (cell.v === undefined && cell.z && cell.t === 'n') {
          // allowed: empty numeric cell with format
        }
      });
    });
  }
};

export const convertDataToFileWithStyleOld = async (
  dataToConvert: { [sheetName: string]: IExcelDataCellWithStyles[][] },
  options?: {
    width?: { [sheetName: string]: { [colId: string]: number } };
    sheetOptions?: {
      [sheetName: string]: {
        showGridLines?: boolean;
      };
    };
  },
) => {

  checkData(dataToConvert);

  const wb = XLSXStyle.utils.book_new();
  wb.Props = {
    Title: 'data',
    Subject: 'data',
    Author: 'Black Feather',
    CreatedDate: new Date(),
  };

  Object.entries(dataToConvert).forEach(([sheetName, data]) => {
    const ws = XLSXStyle.utils.aoa_to_sheet(data);
    const uniqueLetters = new Set<string>();
    Object.keys(ws).forEach((columnKey) => {
      const letter = columnKey.replace(/[0-9]/g, '');
      uniqueLetters.add(letter);
    });
    const uniqueLettersArray = Array.from(uniqueLetters);
    ws['!cols'] = uniqueLettersArray.map((columnId) => ({ wch: options?.width?.[sheetName]?.[columnId] ?? defaultColumnWidth }));
    const sheetOpts = options?.sheetOptions?.[sheetName];
    if (sheetOpts?.showGridLines === false) {
      ws['!sheetPr'] = { pageSetUpPr: { showGridLines: false } };
    }
    XLSXStyle.utils.book_append_sheet(wb, ws, sheetName);
  });
  return XLSXStyle.write(wb, { bookType: 'xlsx', type: 'buffer' });
};

export const convertDataToFileWithStyle = async (
  dataToConvert: { [sheetName: string]: IExcelJsCell[][] },
  options?: {
    width?: { [sheetName: string]: { [colId: string]: number } };
    sheetOptions?: {
      [sheetName: string]: {
        showGridLines?: boolean;
      };
    };
    conditionalFormatting?: {
      [sheetName: string]: ExcelJS.ConditionalFormattingOptions[];
    };
  },
): Promise<Buffer> => {

  checkData(dataToConvert);

  const workbook = new ExcelJS.Workbook();
  workbook.creator = 'Black Feather';
  workbook.created = new Date();

  for (const [sheetName, data] of Object.entries(dataToConvert)) {
    const sheet = workbook.addWorksheet(sheetName, {
      views: [
        {
          showGridLines: options?.sheetOptions?.[sheetName]?.showGridLines !== false,
        },
      ],
    });

    data.forEach((row, rowIndex) => {
      const excelRow = sheet.getRow(rowIndex + 1);
      row.forEach((cell, colIndex) => {
        const excelCell = excelRow.getCell(colIndex + 1);
        excelCell.value = cell.v;

        // TYPES
        if (cell.t === 'n') {
          excelCell.numFmt = cell.z || '#,##0.00';
        }

        if (cell.hyperlink) {
          excelCell.value = {
            text: cell.v?.toString() ?? '',
            hyperlink: cell.hyperlink,
          };

          // (Необязательно) стиль как ссылка
          excelCell.font = { color: { argb: '0563C1' }, underline: true };
        } else {
          excelCell.value = cell.v;
        }

        // STYLES
        if (cell.s) {
          if (cell.s.font) {
            excelCell.font = {
              name: 'Calibri',
              size: 11,
              ...cell.s.font,
            };
          }
          if (cell.s.alignment) {
            excelCell.alignment = {
              vertical: 'middle',
              horizontal: 'left',
              ...cell.s.alignment,
            };
          }
          if (cell.s.fill) {
            if (cell.s.fill.type === 'pattern') {
              excelCell.fill = {
                type: 'pattern',
                pattern: cell.s.fill.pattern ?? 'solid',
                ...cell.s.fill,
              };
            } else {
              excelCell.fill = cell.s.fill;
            }
          }
          if (cell.s.border) {
            excelCell.border = cell.s.border;
          }
        }
      });
    });

    // WIDTH
    const colLetters = new Set<string>();
    const maxColumns = Math.max(...data.map(row => row.length));
    for (let i = 0; i < maxColumns; i++) {
      const colId = String.fromCharCode(65 + i);
      colLetters.add(colId);
    }
    sheet.columns = Array.from(colLetters).map((colId) => ({
      width: options?.width?.[sheetName]?.[colId] ?? defaultColumnWidth,
    }));

    const formatting = options?.conditionalFormatting?.[sheetName];

    if (formatting) {
      formatting.forEach((rule) => {
        sheet.addConditionalFormatting(rule);
      });
    }
  }

  const buffer = await workbook.xlsx.writeBuffer();
  return Buffer.from(buffer);
};


export const addHeaderToData = (header: { [key: string]: string }, data: object[]) => {
  const filterAndReorder = (item: object) => {
    const newItem = {};
    Object.keys(header).forEach(key => {
      if (item.hasOwnProperty(key)) {
        newItem[key] = item[key];
      }
    });
    return newItem;
  };
  const processedItems = data.map(filterAndReorder);
  return [header, ...processedItems];
};

export const getColumnsWidths = (sheetNames: string[], columnWidth: { [columnLetter: string]: number }) => {
  return sheetNames.reduce((acc, sheetName) => {
    return {
      ...acc,
      [sheetName]: columnWidth,
    };
  }, {});
};

export const getSheetsOptions = (sheetNames: string[], options: { showGridLines: boolean }) => {
  return sheetNames.reduce(
    (acc, sheetName) => ({
      ...acc,
      [sheetName]: options,
    }),
    {},
  );
};
