/**
 * Redmine Statistics View
 *
 * Default landing view for a Redmine feature instance. Reads aggregated
 * stats from the local mirror (fast, even at 20k+ tickets) and renders
 * KPIs + charts via ``FormGeneratorReport``. The built-in
 * ``dateRangeSelector`` mounts the shared ``PeriodPicker`` -- no extra
 * wiring needed.
 *
 * Buckets returned by the backend are mapped to ``ReportSection``s here
 * (frontend does the UI shape; backend stays storage-oriented).
 */

import React, { useCallback, useEffect, useMemo, useState } from 'react';

import { useApiRequest } from '../../../hooks/useApi';
import { useInstanceId } from '../../../hooks/useCurrentInstance';
import { useLanguage } from '../../../providers/language/LanguageContext';
import {
  RedmineFieldSchema,
  RedmineStats,
  getRedmineSchemaApi,
  getRedmineStatsApi,
} from '../../../api/redmineApi';

import { FormGeneratorReport } from '../../../components/FormGenerator/FormGeneratorReport';
import type {
  ReportSection,
  ReportFilterState,
  ReportDateRangeSelectorConfig,
  ReportFilterConfig,
} from '../../../components/FormGenerator/FormGeneratorReport';
import { toIsoDate } from '../../../components/PeriodPicker';

import styles from './RedmineViews.module.css';

// ============================================================================
// Helpers -- map raw backend buckets to ReportSection[]
// ============================================================================

// Format counts as integers ("Einheiten") -- prevents the chart components
// from falling back to their default currency formatter (CHF).
const _fmtUnits = (v: number): string => {
  if (!Number.isFinite(v)) return '0';
  return Math.round(v).toLocaleString('de-CH');
};

const _buildSections = (
  stats: RedmineStats,
  t: (key: string, vars?: Record<string, any>) => string,
): ReportSection[] => {
  const sections: ReportSection[] = [];

  // ---- KPI tiles --------------------------------------------------------
  sections.push({
    type: 'kpiGrid',
    span: 'full',
    items: [
      { label: t('Tickets gesamt'), value: stats.kpis.total },
      { label: t('Offen'), value: stats.kpis.open, color: '#4A6FA5' },
      { label: t('Geschlossen'), value: stats.kpis.closed, color: '#38A169' },
      { label: t('Im Zeitraum erstellt'), value: stats.kpis.createdInPeriod },
      { label: t('Im Zeitraum geschlossen'), value: stats.kpis.closedInPeriod },
      {
        label: t('Ohne Userstory (Orphans)'),
        value: stats.kpis.orphans,
        color: stats.kpis.orphans > 0 ? '#C53030' : undefined,
      },
    ],
  });

  // ---- Snapshot chart: total tickets vs. open per bucket end ----------
  // ``cumTotal`` and ``cumOpen`` are computed server-side and are SNAPSHOT
  // values (state at the end of each bucket), not flow numbers. The
  // difference between the two lines is the cumulative number of closed
  // tickets up to that point in time.
  if (stats.throughput.length > 0) {
    const snapshotData = stats.throughput.map(b => ({
      date: b.label,
      total: b.cumTotal,
      open: b.cumOpen,
    }));
    sections.push({
      type: 'lineChart',
      span: 'full',
      title: t('Bestand pro {bucket}: Total vs. Offen', { bucket: stats.bucket }),
      description: t('Snapshot am Ende jeder Periode: wie viele Tickets es zu diesem Zeitpunkt gibt (Total) und wie viele davon noch offen sind. Die Luecke zwischen den Linien sind die bis dahin geschlossenen Tickets.'),
      data: snapshotData,
      series: [
        { key: 'total', label: t('Total'), color: '#4A6FA5' },
        { key: 'open', label: t('Offen'), color: '#DD6B20' },
      ],
      formatValue: _fmtUnits,
    });
  }

  // ---- Status per tracker (stacked-like via horizontal bar per tracker)
  if (stats.statusByTracker.length > 0) {
    const statusKeys = new Set<string>();
    stats.statusByTracker.forEach(row => {
      Object.keys(row.countsByStatus).forEach(k => statusKeys.add(k));
    });
    const totals: Record<string, number> = {};
    stats.statusByTracker.forEach(row => {
      Object.entries(row.countsByStatus).forEach(([s, n]) => {
        totals[s] = (totals[s] || 0) + n;
      });
    });
    sections.push({
      type: 'pieChart',
      span: 'half',
      title: t('Status-Verteilung (gesamt)'),
      donut: true,
      data: Object.entries(totals)
        .sort(([, a], [, b]) => b - a)
        .map(([status, count]) => ({ key: status, value: count })),
      formatValue: _fmtUnits,
    });

    sections.push({
      type: 'horizontalBar',
      span: 'half',
      title: t('Tickets pro Tracker'),
      data: stats.statusByTracker
        .slice()
        .sort((a, b) => b.total - a.total)
        .map(row => ({
          key: row.trackerName,
          value: row.total,
        })),
      formatValue: _fmtUnits,
    });
  }

  // ---- Top assignees ---------------------------------------------------
  if (stats.topAssignees.length > 0) {
    sections.push({
      type: 'horizontalBar',
      span: 'half',
      title: t('Top 10 Zugewiesene (offene Tickets)'),
      description: t('Offene Tickets nach zugewiesener Person -- zeigt Auslastung.'),
      data: stats.topAssignees.map(a => ({
        key: a.name,
        value: a.open,
      })),
      formatValue: _fmtUnits,
    });
  }

  // ---- Relation distribution ------------------------------------------
  if (stats.relationDistribution.length > 0) {
    sections.push({
      type: 'pieChart',
      span: 'half',
      title: t('Beziehungsarten'),
      donut: true,
      data: stats.relationDistribution.map(r => ({
        key: r.relationType,
        value: r.count,
      })),
      formatValue: _fmtUnits,
    });
  }

  // ---- Backlog aging --------------------------------------------------
  if (stats.backlogAging.length > 0) {
    sections.push({
      type: 'barChart',
      span: 'full',
      title: t('Backlog-Alter (offene Tickets)'),
      description: t('Verteilung offener Tickets nach Alter -- hilft alte Leichen zu finden.'),
      data: stats.backlogAging.map(b => ({
        key: b.label,
        value: b.count,
      })),
      color: '#DD6B20',
      formatValue: _fmtUnits,
    });
  }

  return sections;
};

// ============================================================================
// Main view
// ============================================================================

type BucketSize = 'day' | 'week' | 'month';

// Translate the polymorphic ``ReportFilterState.filters`` value for one
// multiselect key into a clean number[] and only call ``setter`` if the
// list actually changed (prevents an infinite render loop when the
// FormGenerator re-emits the same state).
const _applyMultiselectFilter = (
  raw: any,
  current: number[],
  setter: (next: number[]) => void,
): void => {
  let next: number[] | null = null;
  if (Array.isArray(raw)) {
    next = raw.map(v => Number(v)).filter(n => !Number.isNaN(n));
  } else if (typeof raw === 'string' && raw !== '') {
    const n = Number(raw);
    if (!Number.isNaN(n)) next = [n];
  } else if (!raw) {
    next = [];
  }
  if (next == null) return;
  if (next.length === current.length && next.every((v, i) => v === current[i])) return;
  setter(next);
};

export const RedmineStatsView: React.FC = () => {
  const { t } = useLanguage();
  const { request } = useApiRequest();
  const instanceId = useInstanceId();

  const [schema, setSchema] = useState<RedmineFieldSchema | null>(null);
  const [schemaError, setSchemaError] = useState<string | null>(null);

  const [stats, setStats] = useState<RedmineStats | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const [dateFrom, setDateFrom] = useState<string | undefined>(undefined);
  const [dateTo, setDateTo] = useState<string | undefined>(undefined);
  const [bucket, setBucket] = useState<BucketSize>('week');
  const [trackerIds, setTrackerIds] = useState<number[]>([]);
  const [categoryIds, setCategoryIds] = useState<number[]>([]);
  const [statusFilter, setStatusFilter] = useState<'*' | 'open' | 'closed'>('*');

  // Load schema once -- we need trackers for the filter dropdown.
  const _loadSchema = useCallback(async () => {
    if (!instanceId) return;
    try {
      const res = await getRedmineSchemaApi(request, instanceId);
      setSchema(res);
    } catch (e: any) {
      setSchemaError(e?.response?.data?.detail || e?.message || t('Schema-Laden fehlgeschlagen'));
    }
  }, [request, instanceId, t]);

  useEffect(() => { _loadSchema(); }, [_loadSchema]);

  // Load stats whenever the filters change.
  const _loadStats = useCallback(async () => {
    if (!instanceId) return;
    setLoading(true);
    setError(null);
    try {
      const res = await getRedmineStatsApi(request, instanceId, {
        dateFrom,
        dateTo,
        bucket,
        trackerIds: trackerIds.length > 0 ? trackerIds : undefined,
        categoryIds: categoryIds.length > 0 ? categoryIds : undefined,
        statusFilter,
      });
      setStats(res);
    } catch (e: any) {
      setError(e?.response?.data?.detail || e?.message || t('Statistik-Laden fehlgeschlagen'));
      setStats(null);
    } finally {
      setLoading(false);
    }
  }, [request, instanceId, dateFrom, dateTo, bucket, trackerIds, categoryIds, statusFilter, t]);

  useEffect(() => { _loadStats(); }, [_loadStats]);

  // ---- FormGeneratorReport filter configuration -----------------------
  const dateRangeSelector = useMemo<ReportDateRangeSelectorConfig>(() => ({
    enabled: true,
    direction: 'past',
    defaultPresetKind: 'thisQuarter',
    enabledPresets: [
      'allTime',
      'thisMonth', 'lastMonth', 'thisQuarter', 'lastQuarter',
      'ytd', 'lastYear', 'last12Months', 'lastN', 'custom',
    ],
  }), []);

  const filterConfigs = useMemo<ReportFilterConfig[]>(() => {
    const configs: ReportFilterConfig[] = [
      {
        key: 'bucket',
        label: t('Gruppierung'),
        type: 'select',
        defaultValue: bucket,
        options: [
          { value: 'day', label: t('Tag') },
          { value: 'week', label: t('Woche') },
          { value: 'month', label: t('Monat') },
        ],
      },
      {
        key: 'statusFilter',
        label: t('Status'),
        type: 'select',
        defaultValue: statusFilter,
        options: [
          { value: '*', label: t('Alle') },
          { value: 'open', label: t('Nur offen') },
          { value: 'closed', label: t('Nur geschlossen') },
        ],
      },
    ];
    if (schema && schema.trackers.length > 0) {
      configs.push({
        key: 'trackerIds',
        label: t('Tracker'),
        type: 'multiselect',
        options: schema.trackers.map(tr => ({
          value: String(tr.id),
          label: tr.name,
        })),
        placeholder: t('Alle Tracker'),
      });
    }
    if (schema && schema.categories.length > 0) {
      configs.push({
        key: 'categoryIds',
        label: t('Kategorie'),
        type: 'multiselect',
        options: schema.categories.map(cat => ({
          value: String(cat.id),
          label: cat.name,
        })),
        placeholder: t('Alle Kategorien'),
      });
    }
    return configs;
  }, [t, bucket, statusFilter, schema]);

  const _handleFilterChange = useCallback((filterState: ReportFilterState) => {
    if (filterState.periodValue) {
      // "Alle" = no date filter. Drop the sentinel range so the backend
      // aggregates over the full history instead of clamping to 1970--2999.
      if (filterState.periodValue.preset.kind === 'allTime') {
        setDateFrom(undefined);
        setDateTo(undefined);
      } else {
        setDateFrom(filterState.periodValue.fromDate);
        setDateTo(filterState.periodValue.toDate);
      }
    } else if (filterState.dateRange) {
      setDateFrom(toIsoDate(filterState.dateRange.from));
      setDateTo(toIsoDate(filterState.dateRange.to));
    }

    const f = filterState.filters || {};
    if (typeof f.bucket === 'string' && f.bucket !== bucket) {
      setBucket(f.bucket as BucketSize);
    }
    if (typeof f.statusFilter === 'string' && f.statusFilter !== statusFilter) {
      const next = f.statusFilter as '*' | 'open' | 'closed';
      if (next === '*' || next === 'open' || next === 'closed') {
        setStatusFilter(next);
      }
    }
    _applyMultiselectFilter(f.trackerIds, trackerIds, setTrackerIds);
    _applyMultiselectFilter(f.categoryIds, categoryIds, setCategoryIds);
  }, [bucket, statusFilter, trackerIds, categoryIds]);

  // ---- Derived report sections ----------------------------------------
  const sections = useMemo<ReportSection[]>(() => {
    if (!stats) return [];
    return _buildSections(stats, t);
  }, [stats, t]);

  if (!instanceId) {
    return <div className={styles.placeholder}>{t('Keine Feature-Instanz ausgewaehlt')}</div>;
  }

  return (
    <div className={styles.pageWide}>
      <h2 className={styles.heading}>{t('Redmine -- Statistik')}</h2>
      <p className={styles.subheading}>
        {t('Aggregiert aus dem lokalen Mirror. Filter werden serverseitig angewendet; Zeitraum steuert auch die "im Zeitraum"-KPIs.')}
      </p>

      {schemaError && <div className={styles.alertErr}>{schemaError}</div>}
      {error && <div className={styles.alertErr}>{error}</div>}

      <FormGeneratorReport
        title={stats
          ? t('{total} Tickets ({open} offen, {closed} geschlossen)', {
              total: stats.kpis.total,
              open: stats.kpis.open,
              closed: stats.kpis.closed,
            })
          : undefined}
        sections={sections}
        loading={loading}
        noDataMessage={t('Keine Tickets im Mirror. Starte zuerst den Sync auf der Einstellungen-Seite.')}
        dateRangeSelector={dateRangeSelector}
        filters={filterConfigs}
        onFilterChange={_handleFilterChange}
      />
    </div>
  );
};

export default RedmineStatsView;
