import { fromPairs, merge, omit, times, toPairs } from 'lodash';
import moment, { Moment } from 'moment-timezone';
import { api } from 'src/boot/axios';
import { capitalize } from 'src/helpers/formatters';
import { Visitas } from 'src/models';
import * as XLSX from 'xlsx-js-style';
import { fields, fincasFields } from './fields';
import { findColumnByLabel, findRowByLabel } from './utils';

type ReportRowValue = { label: string; value: number };
type ReportRow = { label: string; values: ReportRowValue[]; important?: boolean };

export type Period = { from: string; to: string };
export type ReportableVisita = Visitas & { place: Record<string, any>; mapeo: Record<string, any> };
export type ReportableContrato = Record<string, any>;
const LABELS = {
  currentPeriod: 'Semana Actual',
  previousPeriod: 'Semana Anterior',
  accumulated: 'Acumulado desde apertura',
};
export class NaturgyReporter {
  private visitas: ReportableVisita[] = [];
  private contratos: ReportableContrato[] = [];
  private alcaldias: string[] = [];
  private days: Moment[] = [];
  private data: {
    dailyCounts?: { label: string; rows: ReportRow[]; important?: boolean }[];
    dailyCountsSummary?: ReportRow[];
    fincas?: any[];
  } = {};
  fincas: Record<string, any[]> = {};

  async buildReport(period: Period) {
    console.log(fincasFields)
    await this.fetchData(period);
    this.buildRawReport();
  }

  private async fetchData(period: Period) {
    await Promise.all([this.getVisitas(period), this.getContratos(period), this.getAlcaldias()]);
    this.getFincas()
    console.log(this.contratos);
    this.getMomentDays(period);
  }

  private getVisitasInDay(visitas: ReportableVisita[], date: Moment) {
    return visitas.filter((v) => moment(v.FECHA_VISITA).isSame(date, 'day'));
  }
  private getContratosInDay(contratos: ReportableContrato[], date: Moment) {
    return contratos.filter((c) => {
      const historial: any[] = c.HISTORIAL;
      return moment(c.FECHA_CAPTADO).isSame(date, 'day') || historial.some((el) => moment(el.FECHA_CREADO).isSame(date, 'day'));
    });
  }

  buildRawReport() {
    this.data = {
      dailyCounts: this.alcaldias.map((alcaldia) => {
        const visitasInAlcaldia = this.visitas.filter((v) => v.place?.alcaldia === alcaldia);
        const contratosInAlcaldia = this.contratos.filter((c) => c.place?.alcaldia === alcaldia);
        const rows = fields.map((field) => {
          const label = field.label;
          const values = this.days.map((day) => {
            const data = { visitas: this.getVisitasInDay(visitasInAlcaldia, day), contratos: this.getContratosInDay(contratosInAlcaldia, day) };
            const value = field.calculator(data);
            const label = capitalize(day.format('dddd DD'));
            return { label, value };
          });
          return {
            label,
            values,
            important: field.important,
          };
        });

        return {
          label: alcaldia,
          rows,
        };
      }),
      dailyCountsSummary: fields.map((field) => ({ ...field, values: this.alcaldias.map((item) => ({ label: capitalize(item, true), value: 0 })) })),
      // fincas: this.alcaldias.map((alcaldia) => {
      //   const visitasInAlcaldia = this.visitas.filter((v) => v.place?.alcaldia === alcaldia);
      //   const places = uniqBy(visitasInAlcaldia.map((v) => v.place), 'id')
      //   const label = alcaldia;
      //   // const table = 
      //   // const values = fincasFields.map((field) => {
      //   //   const label = field.label;
      //   //   const value = field.calculator({ visitas: visitasInAlcaldia, contratos: this.contratos });
      //   //   return { label, value };
      //   // })
      // })
    };
  }

  getDataAsTables() {
    return {};
  }

  private formatRow(row: ReportRow, fieldLabel = 'Resumen Operativo') {
    if (!row) return {};
    return {
      [fieldLabel]: { v: row.label, s: row.important ? new StyleGenerator().align('left').border().bold().apply() : fieldStyle },
      ...fromPairs(row.values.map((item) => [item.label, { v: item.value, t: 'n', s: row.important ? importantStyle : baseStyle }])),
      [LABELS.currentPeriod]: { v: 0, t: 'n', s: row.important ? importantStyle : baseStyle },
      [LABELS.previousPeriod]: { v: 0, t: 'n', s: row.important ? importantStyle : baseStyle },
      [LABELS.accumulated]: { v: 0, t: 'n', s: row.important ? importantStyle : baseStyle },
    };
  }

  exportToExcel() {
    const wb = XLSX.utils.book_new();
    const summaryRows = this.data.dailyCountsSummary!.map((row) => this.formatRow(row));
    const summarySheet = XLSX.utils.json_to_sheet(summaryRows);
    console.log('fincas', this.data.fincas);
   

    this.addStyles(summarySheet);
    XLSX.utils.book_append_sheet(wb, summarySheet, 'Resumen');
    this.data.dailyCounts?.forEach((alcaldia) => {
      const rows = alcaldia.rows.map((row) => this.formatRow(row));
      const ws = XLSX.utils.json_to_sheet(rows);
      this.addStyles(ws);
      XLSX.utils.book_append_sheet(wb, ws, capitalize(alcaldia.label, true));
      this.addCurrentPeriodSum(ws);

      // XLSX.utils.sheet_add_json(summarySheet, this.data.fincas, { origin: { r: 20, c: 0 } });
    });
    this.addSumCalculations(wb);

    XLSX.writeFile(wb, `Reporte Naturgy semana ${this.days[0].format('DD-MM-YYYY')}.xlsx`);
  }


  private addStyles(sheet: XLSX.WorkSheet) {
    const range = XLSX.utils.decode_range(sheet['!ref'] || '');
    const lastCol = range.e.c;
    for (let i = 0; i <= lastCol; i++) {
      const col = XLSX.utils.encode_col(i);
      const cell = sheet[`${col}1`];
      if (cell) {
        cell.s = headerStyle;
      }
    }

    sheet['!cols'] = times(20, () => ({ wch: 15 }));
  }

  private addCurrentPeriodSum(sheet: XLSX.WorkSheet) {
    const range = XLSX.utils.decode_range(sheet['!ref'] || '');
    const lastRow = range.e.r + 1;
    const col = findColumnByLabel(sheet, LABELS.currentPeriod);
    const lastCol = XLSX.utils.encode_col(XLSX.utils.decode_col(col!) - 1);
    for (let i = 1; i < lastRow; i++) {
      const row = XLSX.utils.encode_row(i);
      const cell = sheet[`${col}${row}`];
      cell.t = 'n';
      cell.f = `SUM(B${row}:${lastCol}${row})`;
    }
  }

  private addSumCalculations(wb: XLSX.WorkBook) {
    const sheet = wb.Sheets['Resumen'];
    const range = XLSX.utils.decode_range(sheet['!ref'] || '').e;
    const lastRow = range.r + 1;
    const lastCol = range.c;
    const alcaldias = times(lastCol - 3, (i) => sheet[`${XLSX.utils.encode_col(i + 1)}1`]?.v);
    for (let i = 1; i < lastRow; i++) {
      const row = XLSX.utils.encode_row(i);
      const field = sheet[`A${row}`]?.v;
      if (!field) continue;
      alcaldias.forEach((alcaldia) => {
        const col = findColumnByLabel(sheet, alcaldia);
        const _sheet = wb.Sheets[alcaldia];
        const _col = findColumnByLabel(_sheet, LABELS.currentPeriod);
        const _row = findRowByLabel(_sheet, field);
        if (!_col || !_row) return;
        const cellCode = `${_col}${_row}`;
        const cell = sheet[`${col}${row}`];
        cell.t = 'n';
        cell.f = `'${alcaldia}'!${cellCode}`;
      });
    }
    this.addCurrentPeriodSum(sheet);
  }

  private getMomentDays(period: Period) {
    const start = moment(period.from);
    const end = moment(period.to);
    if (end.isBefore(start)) {
      return [];
    }
    const days: Moment[] = [];
    const current = start.clone().startOf('day');
    while (current.isSameOrBefore(end)) {
      days.push(current.clone());
      current.add(1, 'day');
    }
    this.days = days;
  }

  private async getVisitas(period: Period) {
    this.visitas = await api
      .get('operacion/visitas', {
        params: {
          custom: {
            period,
            add_places: true,
          },
        },
      })
      .then((res) => res.data);
  }

  private async getContratos(period: Period) {
    this.contratos = await api
      .get('operacion/captaciones', {
        params: {
          custom: {
            period,
            add_places: true,
          },
          byEqualCol: { 'captaciones.ID_GAMA': [27, 28] },
        },
      })
      .then((res) => (res.data || []).flatMap((el) => el.HISTORIAL.map((h) => ({ ...omit(el, 'HISTORIAL'), timestamp: h }))));
  }

  private async getAlcaldias() {
    this.alcaldias = await api.get('public/collections/alcaldias').then((res) => res.data.map(({ label }) => label));
  }

  private async getFincas() {
    // obtener solo las asignadas en la semana actual
    const fincas = await Promise.all(this.alcaldias.map(alcaldia => api.get('places/count', { 
    params: { key: 'alcaldia', values: [alcaldia] }}).then((res) => res.data)))
    this.fincas = fromPairs(fincas.flatMap(f => toPairs(f)))
    console.log('fincas', this.fincas);
  }
}

class StyleGenerator {
  private styles: Record<string, any> = {};

  bold() {
    this.styles = merge(this.styles, { font: { bold: true } });
    return this;
  }

  align(position: 'left' | 'right' | 'center') {
    const alignment = {
      horizontal: position,
      vertical: 'center',
      wrapText: true,
    };
    this.styles = merge(this.styles, { alignment });
    return this;
  }

  border() {
    const border = {
      top: { style: 'thin', color: { rgb: '000000' } },
      bottom: { style: 'thin', color: { rgb: '000000' } },
      left: { style: 'thin', color: { rgb: '000000' } },
      right: { style: 'thin', color: { rgb: '000000' } },
    };
    this.styles = merge(this.styles, { border });
    return this;
  }

  fill(color: string = 'DAE9F8') {
    const fill = {
      patternType: 'solid',
      fgColor: { rgb: color },
    };
    this.styles = merge(this.styles, { fill });
    return this;
  }

  apply() {
    return this.styles;
  }
}

const baseStyle = new StyleGenerator().align('center').border().apply();
const importantStyle = new StyleGenerator().align('center').border().bold().apply();
const fieldStyle = new StyleGenerator().align('left').border().apply();
const headerStyle = new StyleGenerator().bold().align('center').border().fill('DAE9F8').apply();
