/**
 * Reports Controller
 *
 * Handles search, filtering, and chart rendering for the Reports page.
 * Supports range queries on amounts and invoice numbers.
 */

import Fuse from 'fuse.js';
import { createChart } from '@sevenweirdpeople/swp-charting';

interface SalesDataItem {
  index: number;
  invoice: string;
  date: string;
  customer: string;
  employee: string;
  services: string;
  amount: string;
  payment: string;
  status: string;
}

interface ParsedQuery {
  textQuery: string;
  minAmount: number | null;
  maxAmount: number | null;
  minInvoice: number | null;
  maxInvoice: number | null;
  invoicePrefix: string | null;
}

interface MonthMapping {
  year: number;
  month: number;
}

interface ChartClickEvent extends CustomEvent {
  detail: {
    type: 'line' | 'bar' | 'pie';
    x?: string;
    points: Array<{ id?: string; seriesName: string; value: number; color: string; unit?: string; percent?: number }>;
  };
}

interface ChartSelectEvent extends CustomEvent {
  detail: {
    type: 'line' | 'bar';
    points: Array<{ id?: string; seriesName: string; seriesIndex: number; value: number; color: string; unit?: string }>;
    bounds: { x1: string; x2: string; y1: number; y2: number };
  };
}

interface DataPoint {
  x: string;
  y: number;
}

interface SeriesConfig {
  name: string;
  color: string;
  type: 'bar' | 'pie' | 'line';
  data: DataPoint[];
  unit?: string;
  pie?: { innerRadius: number; outerRadius: number };
}

interface ChartDataConfig {
  series: SeriesConfig[];
}

interface ReportsData {
  revenue: ChartDataConfig;
  payment: ChartDataConfig;
  hours: ChartDataConfig;
  absence: ChartDataConfig;
}

export class ReportsController {
  private searchInput: HTMLInputElement | null = null;
  private dateFromInput: HTMLInputElement | null = null;
  private dateToInput: HTMLInputElement | null = null;
  private statusFilter: HTMLSelectElement | null = null;
  private paymentFilter: HTMLSelectElement | null = null;
  private tableRows: NodeListOf<HTMLElement> | null = null;
  private salesData: SalesDataItem[] = [];
  private fuse: Fuse<SalesDataItem> | null = null;

  // Chart references for lazy initialization
  private revenueChart: ReturnType<typeof createChart> | null = null;
  private paymentChart: ReturnType<typeof createChart> | null = null;
  private hoursChart: ReturnType<typeof createChart> | null = null;
  private absenceChart: ReturnType<typeof createChart> | null = null;
  private salesChartsInitialized = false;
  private hoursChartsInitialized = false;

  // Chart data loaded from JSON
  private chartData: ReportsData | null = null;

  // Map pie chart series names to payment filter values
  private readonly paymentMap: Record<string, string> = {
    'Kort': 'card',
    'MobilePay': 'mobilepay',
    'Kontant': 'cash',
    'Faktura': 'invoice',
    'Fordelskort': 'giftcard',
  };

  // Map status badge text to filter values
  private readonly statusMap: Record<string, string> = {
    'Betalt': 'paid',
    'Afventer': 'pending',
    'Krediteret': 'credited',
  };

  // Map payment badge text to filter values
  private readonly paymentTextMap: Record<string, string> = {
    'Kort': 'card',
    'MobilePay': 'mobilepay',
    'Kontant': 'cash',
    'Faktura': 'invoice',
    'Fordelskort': 'giftcard',
  };

  // Map month names to year/month (based on 2024/2025 fiscal year)
  private readonly monthMap: Record<string, MonthMapping> = {
    'Feb': { year: 2024, month: 2 },
    'Mar': { year: 2024, month: 3 },
    'Apr': { year: 2024, month: 4 },
    'Maj': { year: 2024, month: 5 },
    'Jun': { year: 2024, month: 6 },
    'Jul': { year: 2024, month: 7 },
    'Aug': { year: 2024, month: 8 },
    'Sep': { year: 2024, month: 9 },
    'Okt': { year: 2024, month: 10 },
    'Nov': { year: 2024, month: 11 },
    'Dec': { year: 2024, month: 12 },
    'Jan': { year: 2025, month: 1 },
  };

  constructor() {
    this.searchInput = document.getElementById('searchInput') as HTMLInputElement | null;
    this.dateFromInput = document.getElementById('dateFrom') as HTMLInputElement | null;
    this.dateToInput = document.getElementById('dateTo') as HTMLInputElement | null;
    this.statusFilter = document.getElementById('statusFilter') as HTMLSelectElement | null;
    this.paymentFilter = document.getElementById('paymentFilter') as HTMLSelectElement | null;
    this.tableRows = document.querySelectorAll<HTMLElement>('swp-card.sales-table swp-data-table-row');

    if (this.tableRows?.length) {
      this.buildSearchData();
      this.initializeFuse();
      this.setupListeners();
      this.setupFilterListeners();
    }

    this.setupTabs();
    this.setupPeriodSelector();
    this.setupChartEvents();

    // Load chart data from JSON and initialize charts
    this.loadChartData().then(() => {
      this.initializeSalesCharts();
    });
  }

  /**
   * Load chart data from JSON file
   */
  private async loadChartData(): Promise<void> {
    try {
      const response = await fetch('/data/reports-data.json');
      if (!response.ok) return;
      this.chartData = await response.json() as ReportsData;
    } catch {
      console.error('Failed to load reports chart data');
    }
  }

  /**
   * Build searchable data from table rows
   */
  private buildSearchData(): void {
    if (!this.tableRows) return;

    this.salesData = Array.from(this.tableRows).map((row, index) => {
      const cells = row.querySelectorAll('swp-data-table-cell');
      return {
        index,
        invoice: cells[0]?.textContent?.trim() || '',
        date: cells[1]?.textContent?.trim() || '',
        customer: cells[2]?.textContent?.trim() || '',
        employee: cells[3]?.textContent?.trim() || '',
        services: cells[4]?.textContent?.trim() || '',
        amount: cells[5]?.textContent?.trim() || '',
        payment: cells[6]?.textContent?.trim() || '',
        status: cells[7]?.textContent?.trim() || ''
      };
    });
  }

  /**
   * Initialize Fuse.js for fuzzy text search
   */
  private initializeFuse(): void {
    this.fuse = new Fuse(this.salesData, {
      keys: ['invoice', 'customer', 'employee', 'services', 'amount', 'payment', 'status'],
      threshold: 0.3,
      ignoreLocation: true
    });
  }

  /**
   * Setup search event listener
   */
  private setupListeners(): void {
    this.searchInput?.addEventListener('input', (e) => this.handleSearch(e));
  }

  /**
   * Setup filter event listeners (date, status, payment)
   */
  private setupFilterListeners(): void {
    this.dateFromInput?.addEventListener('change', () => this.applyAllFilters());
    this.dateToInput?.addEventListener('change', () => this.applyAllFilters());
    this.statusFilter?.addEventListener('change', () => this.applyAllFilters());
    this.paymentFilter?.addEventListener('change', () => this.applyAllFilters());
  }

  /**
   * Apply all filters (search + date + status + payment)
   */
  private applyAllFilters(): void {
    const searchQuery = this.searchInput?.value.trim() || '';
    const dateFrom = this.dateFromInput?.value || '';
    const dateTo = this.dateToInput?.value || '';
    const statusValue = this.statusFilter?.value || '';
    const paymentValue = this.paymentFilter?.value || '';

    // Start with all indices
    let matchedIndices = new Set(this.salesData.map((_, i) => i));

    // Apply search filter (includes range queries)
    if (searchQuery) {
      const parsed = this.parseRangeQuery(searchQuery);

      if (parsed.invoicePrefix !== null) {
        matchedIndices = this.filterByInvoicePrefix(parsed.invoicePrefix);
      }

      if (parsed.minInvoice !== null || parsed.maxInvoice !== null) {
        matchedIndices = this.filterByInvoiceRange(matchedIndices, parsed.minInvoice, parsed.maxInvoice);
      }

      if (parsed.minAmount !== null || parsed.maxAmount !== null) {
        matchedIndices = this.filterByAmountRange(matchedIndices, parsed.minAmount, parsed.maxAmount);
      }

      if (parsed.textQuery) {
        matchedIndices = this.filterByText(matchedIndices, parsed.textQuery);
      }
    }

    // Apply date filter
    if (dateFrom || dateTo) {
      matchedIndices = this.filterByDate(matchedIndices, dateFrom, dateTo);
    }

    // Apply status filter
    if (statusValue) {
      matchedIndices = this.filterByStatus(matchedIndices, statusValue);
    }

    // Apply payment filter
    if (paymentValue) {
      matchedIndices = this.filterByPayment(matchedIndices, paymentValue);
    }

    this.applyFilter(matchedIndices);
  }

  /**
   * Filter by date range
   */
  private filterByDate(indices: Set<number>, fromDate: string, toDate: string): Set<number> {
    return new Set(
      [...indices].filter(i => {
        const dateText = this.salesData[i].date;
        const rowDate = this.parseDanishDate(dateText);
        if (!rowDate) return true; // Keep row if date can't be parsed

        if (fromDate) {
          const from = new Date(fromDate);
          if (rowDate < from) return false;
        }

        if (toDate) {
          const to = new Date(toDate);
          to.setHours(23, 59, 59, 999); // Include the entire end day
          if (rowDate > to) return false;
        }

        return true;
      })
    );
  }

  /**
   * Parse Danish date format "6. jan 2025" to Date object
   */
  private parseDanishDate(text: string): Date | null {
    const monthNames: Record<string, number> = {
      'jan': 0, 'feb': 1, 'mar': 2, 'apr': 3, 'maj': 4, 'jun': 5,
      'jul': 6, 'aug': 7, 'sep': 8, 'okt': 9, 'nov': 10, 'dec': 11
    };

    const match = text.match(/(\d+)\.\s*(\w+)\s*(\d{4})/);
    if (!match) return null;

    const day = parseInt(match[1], 10);
    const monthStr = match[2].toLowerCase();
    const year = parseInt(match[3], 10);
    const month = monthNames[monthStr];

    if (month === undefined) return null;

    return new Date(year, month, day);
  }

  /**
   * Filter by status
   */
  private filterByStatus(indices: Set<number>, statusValue: string): Set<number> {
    return new Set(
      [...indices].filter(i => {
        const statusText = this.salesData[i].status.trim();
        const mappedStatus = this.statusMap[statusText];
        return mappedStatus === statusValue;
      })
    );
  }

  /**
   * Filter by payment method
   */
  private filterByPayment(indices: Set<number>, paymentValue: string): Set<number> {
    return new Set(
      [...indices].filter(i => {
        const paymentText = this.salesData[i].payment.trim();
        // Payment text is like "Kort", "MobilePay", etc.
        const mappedPayment = this.paymentTextMap[paymentText];
        return mappedPayment === paymentValue;
      })
    );
  }

  /**
   * Setup tab switching functionality
   */
  private setupTabs(): void {
    const tabs = document.querySelectorAll<HTMLElement>('swp-tab[data-tab]');

    tabs.forEach(tab => {
      tab.addEventListener('click', () => {
        const targetTab = tab.dataset.tab;
        if (targetTab) {
          this.switchToTab(targetTab);
        }
      });
    });
  }

  /**
   * Switch to a specific tab by name
   */
  private switchToTab(targetTab: string): void {
    const tabs = document.querySelectorAll<HTMLElement>('swp-tab[data-tab]');
    const contents = document.querySelectorAll<HTMLElement>('swp-tab-content[data-tab]');
    const statsRows = document.querySelectorAll<HTMLElement>('swp-stats-row[data-for-tab]');

    tabs.forEach(t => {
      t.classList.toggle('active', t.dataset.tab === targetTab);
    });

    contents.forEach(content => {
      content.classList.toggle('active', content.dataset.tab === targetTab);
    });

    // Toggle stats rows based on active tab
    statsRows.forEach(stats => {
      stats.classList.toggle('active', stats.dataset.forTab === targetTab);
    });

    // Lazy-init charts for the active tab
    if (targetTab === 'sales') {
      if (this.chartData) {
        this.initializeSalesCharts();
      } else {
        this.loadChartData().then(() => this.initializeSalesCharts());
      }
    } else if (targetTab === 'hours') {
      if (this.chartData) {
        this.initializeHoursCharts();
      } else {
        this.loadChartData().then(() => this.initializeHoursCharts());
      }
    }
  }

  /**
   * Handle search input
   */
  private handleSearch(_e: Event): void {
    this.applyAllFilters();
  }

  /**
   * Show all rows
   */
  private showAllRows(): void {
    this.tableRows?.forEach(row => row.style.display = '');
  }

  /**
   * Apply filter to table rows
   */
  private applyFilter(matchedIndices: Set<number>): void {
    this.tableRows?.forEach((row, index) => {
      row.style.display = matchedIndices.has(index) ? '' : 'none';
    });
  }

  /**
   * Filter by invoice prefix (e.g., #18 matches #1847, #1846)
   */
  private filterByInvoicePrefix(prefix: string): Set<number> {
    return new Set(
      this.salesData
        .filter(item => {
          const invoiceDigits = item.invoice.replace(/\D/g, '');
          return invoiceDigits.startsWith(prefix);
        })
        .map(item => item.index)
    );
  }

  /**
   * Filter by invoice number range
   */
  private filterByInvoiceRange(
    indices: Set<number>,
    min: number | null,
    max: number | null
  ): Set<number> {
    return new Set(
      [...indices].filter(i => {
        const invoiceNum = this.parseInvoiceNumber(this.salesData[i].invoice);
        if (invoiceNum === null) return false;
        if (min !== null && invoiceNum < min) return false;
        if (max !== null && invoiceNum > max) return false;
        return true;
      })
    );
  }

  /**
   * Filter by amount range
   */
  private filterByAmountRange(
    indices: Set<number>,
    min: number | null,
    max: number | null
  ): Set<number> {
    return new Set(
      [...indices].filter(i => {
        const amount = this.parseAmountFromText(this.salesData[i].amount);
        if (amount === null) return false;
        if (min !== null && amount < min) return false;
        if (max !== null && amount > max) return false;
        return true;
      })
    );
  }

  /**
   * Filter by text using Fuse.js
   */
  private filterByText(indices: Set<number>, query: string): Set<number> {
    if (!this.fuse) return indices;

    const fuseResults = this.fuse.search(query);
    const textMatches = new Set(fuseResults.map(r => r.item.index));
    return new Set([...indices].filter(i => textMatches.has(i)));
  }

  /**
   * Parse amount string "1.450 kr" -> 1450
   */
  private parseAmountFromText(text: string): number | null {
    const match = text.match(/-?([\d.]+)/);
    if (!match) return null;
    return parseFloat(match[1].replace(/\./g, ''));
  }

  /**
   * Parse invoice number "#1847" -> 1847
   */
  private parseInvoiceNumber(text: string): number | null {
    const match = text.match(/#(\d+)/);
    return match ? parseInt(match[1], 10) : null;
  }

  /**
   * Parse range operators from query
   * Supports:
   * - Amount ranges: >1000, <500, >=500, <=1000, 400-1000
   * - Invoice ranges: #>1845, #<1845, #>=1845, #<=1845, #1840-1845
   * - Invoice prefix: #1847 (matches invoices starting with "1847")
   * - Combined: Maria >1000, #>1845 <500
   */
  private parseRangeQuery(query: string): ParsedQuery {
    let textQuery = query;
    let minAmount: number | null = null;
    let maxAmount: number | null = null;
    let minInvoice: number | null = null;
    let maxInvoice: number | null = null;
    let invoicePrefix: string | null = null;

    // === INVOICE NUMBER RANGES (with # prefix) ===

    // Match #1840-1845 (range)
    let match = textQuery.match(/#(\d+)-(\d+)/);
    if (match) {
      minInvoice = parseInt(match[1], 10);
      maxInvoice = parseInt(match[2], 10);
      textQuery = textQuery.replace(match[0], '').trim();
    }

    // Match #>=
    match = textQuery.match(/#>=\s*(\d+)/);
    if (match) {
      minInvoice = parseInt(match[1], 10);
      textQuery = textQuery.replace(match[0], '').trim();
    }

    // Match #> (but not #>=)
    if (!match) {
      match = textQuery.match(/#>\s*(\d+)/);
      if (match) {
        minInvoice = parseInt(match[1], 10) + 1;
        textQuery = textQuery.replace(match[0], '').trim();
      }
    }

    // Match #<=
    match = textQuery.match(/#<=\s*(\d+)/);
    if (match) {
      maxInvoice = parseInt(match[1], 10);
      textQuery = textQuery.replace(match[0], '').trim();
    }

    // Match #< (but not #<=)
    if (!match) {
      match = textQuery.match(/#<\s*(\d+)/);
      if (match) {
        maxInvoice = parseInt(match[1], 10) - 1;
        textQuery = textQuery.replace(match[0], '').trim();
      }
    }

    // Match #1847 (no operator) - prefix match
    match = textQuery.match(/#(\d+)(?!\d|-)/);
    if (match) {
      invoicePrefix = match[1];
      textQuery = textQuery.replace(/#\d+/, '').trim();
    }

    // === AMOUNT RANGES (no prefix) ===

    // Match range syntax: 400-1000
    match = textQuery.match(/(\d+)-(\d+)/);
    if (match) {
      minAmount = parseFloat(match[1]);
      maxAmount = parseFloat(match[2]);
      textQuery = textQuery.replace(match[0], '').trim();
    }

    // Match >=
    match = textQuery.match(/>=\s*(\d+(?:\.\d+)?)/);
    if (match) {
      minAmount = parseFloat(match[1]);
      textQuery = textQuery.replace(match[0], '').trim();
    }

    // Match > (but not >=)
    match = textQuery.match(/>\s*(\d+(?:\.\d+)?)/);
    if (match) {
      minAmount = parseFloat(match[1]) + 0.01;
      textQuery = textQuery.replace(match[0], '').trim();
    }

    // Match <=
    match = textQuery.match(/<=\s*(\d+(?:\.\d+)?)/);
    if (match) {
      maxAmount = parseFloat(match[1]);
      textQuery = textQuery.replace(match[0], '').trim();
    }

    // Match < (but not <=)
    match = textQuery.match(/<\s*(\d+(?:\.\d+)?)/);
    if (match) {
      maxAmount = parseFloat(match[1]) - 0.01;
      textQuery = textQuery.replace(match[0], '').trim();
    }

    return { textQuery, minAmount, maxAmount, minInvoice, maxInvoice, invoicePrefix };
  }

  /**
   * Setup chart click and selection events
   */
  private setupChartEvents(): void {
    // Single click on chart
    document.addEventListener('swp-chart-click', (e: Event) => {
      const event = e as ChartClickEvent;
      const { type, x, points } = event.detail;

      // Bar chart click -> filter by month
      if (type === 'bar' && x) {
        const month = this.monthMap[x];
        if (month) {
          this.setDateFilter(month, month);
        }
      }

      // Pie chart click -> filter by payment method
      if (type === 'pie' && points?.length > 0) {
        const seriesName = points[0].seriesName;
        const paymentValue = this.paymentMap[seriesName];
        if (paymentValue && this.paymentFilter) {
          this.paymentFilter.value = paymentValue;
          this.applyAllFilters();
        }
      }
    });

    // Range selection on bar chart -> filter by multiple months
    document.addEventListener('swp-chart-select', (e: Event) => {
      const event = e as ChartSelectEvent;
      const { type, bounds } = event.detail;
      if (type !== 'bar' || !bounds) return;

      const startMonth = this.monthMap[bounds.x1];
      const endMonth = this.monthMap[bounds.x2];
      if (startMonth && endMonth) {
        this.setDateFilter(startMonth, endMonth);
      }
    });
  }

  /**
   * Set the date filter inputs to a specific month range
   */
  private setDateFilter(startMonth: MonthMapping, endMonth: MonthMapping): void {
    if (!this.dateFromInput || !this.dateToInput) return;

    // Calculate last day of the end month
    const lastDay = new Date(endMonth.year, endMonth.month, 0).getDate();

    // Format dates as YYYY-MM-DD
    const fromDate = `${startMonth.year}-${String(startMonth.month).padStart(2, '0')}-01`;
    const toDate = `${endMonth.year}-${String(endMonth.month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;

    this.dateFromInput.value = fromDate;
    this.dateToInput.value = toDate;

    // Apply filters to update the table
    this.applyAllFilters();
  }

  /**
   * Initialize sales tab charts (lazy, only when visible)
   */
  private initializeSalesCharts(): void {
    if (this.salesChartsInitialized) return;
    this.revenueChart = this.initRevenueChart();
    this.paymentChart = this.initPaymentChart();
    this.salesChartsInitialized = true;
  }

  /**
   * Initialize hours tab charts (lazy, only when visible)
   */
  private initializeHoursCharts(): void {
    if (this.hoursChartsInitialized) return;
    this.hoursChart = this.initHoursChart();
    this.absenceChart = this.initAbsenceChart();
    this.hoursChartsInitialized = true;
  }

  /**
   * Initialize revenue bar chart (Salgsrapport)
   */
  private initRevenueChart(): ReturnType<typeof createChart> | null {
    const el = document.getElementById('revenueChart');
    if (!el || !this.chartData?.revenue) return null;

    const series = this.chartData.revenue.series;
    if (series.length === 0) return null;

    const categories = series[0].data.map(p => p.x);

    return createChart(el, {
      deferRender: true,
      height: 240,
      xAxis: { categories },
      yAxis: {
        format: (v: number) => `${Math.round(v / 1000)}k`
      },
      series: series
    });
  }

  /**
   * Initialize payment methods pie chart (Salgsrapport)
   */
  private initPaymentChart(): ReturnType<typeof createChart> | null {
    const el = document.getElementById('paymentChart');
    if (!el || !this.chartData?.payment) return null;

    const series = this.chartData.payment.series;
    if (series.length === 0) return null;

    return createChart(el, {
      deferRender: true,
      height: 240,
      series: series,
      tooltip: true,
      legend: { position: 'right', align: 'center' }
    });
  }

  /**
   * Initialize hours per week bar chart (Timerapport)
   */
  private initHoursChart(): ReturnType<typeof createChart> | null {
    const el = document.getElementById('hoursChart');
    if (!el || !this.chartData?.hours) return null;

    const series = this.chartData.hours.series;
    if (series.length === 0) return null;

    // Extract categories from first series
    const categories = series[0]?.data.map(p => p.x) || [];

    return createChart(el, {
      deferRender: true,
      height: 240,
      xAxis: { categories },
      yAxis: { format: (v: number) => v + ' t' },
      series: series,
      legend: { position: 'bottom', align: 'center', gap: 0 }
    });
  }

  /**
   * Initialize absence distribution pie chart (Timerapport)
   */
  private initAbsenceChart(): ReturnType<typeof createChart> | null {
    const el = document.getElementById('absenceChart');
    if (!el || !this.chartData?.absence) return null;

    const series = this.chartData.absence.series;
    if (series.length === 0) return null;

    return createChart(el, {
      deferRender: true,
      height: 240,
      series: series,
      legend: { position: 'right', align: 'center' }
    });
  }

  /**
   * Setup period selector functionality (Timerapport)
   */
  private setupPeriodSelector(): void {
    const buttons = document.querySelectorAll<HTMLButtonElement>('swp-period-selector button');
    buttons.forEach(btn => {
      btn.addEventListener('click', () => {
        buttons.forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
      });
    });
  }
}
