// Data source: NocoDB (DailySales populated by server-side ETL, PilgrimStats)
// Offline mode: caches data to localStorage for resilience

import type {
  MuseumRecord,
  Metrics,
  Filters,
  DateRangeFilters,
  CacheStatus,
  CacheResult,
  FetchResult,
  GroupedData,
  UmrahData,
  NocoDBDailySale,
  DataErrorType
} from '../types';
import { DataError } from '../types';
import { fetchWithRetry } from '../utils/fetchHelpers';

const NOCODB_URL = import.meta.env.VITE_NOCODB_URL || '';
const NOCODB_TOKEN = import.meta.env.VITE_NOCODB_TOKEN || '';
const NOCODB_BASE_ID = import.meta.env.VITE_NOCODB_BASE_ID || '';

// Table IDs discovered dynamically from NocoDB meta API
let discoveredTables: Record<string, string> | null = null;

async function discoverTableIds(): Promise<Record<string, string>> {
  if (discoveredTables) return discoveredTables;
  if (!NOCODB_BASE_ID) throw new Error('NocoDB not configured');

  const res = await fetchWithRetry(
    `${NOCODB_URL}/api/v2/meta/bases/${NOCODB_BASE_ID}/tables`,
    { headers: { 'xc-token': NOCODB_TOKEN } }
  );

  const json = await res.json();
  const tables: Record<string, string> = {};
  for (const t of json.list) {
    tables[t.title] = t.id;
  }

  discoveredTables = tables;
  return tables;
}

async function fetchNocoDBTable<T>(tableId: string, limit: number = 1000): Promise<T[]> {
  let allRecords: T[] = [];
  let offset = 0;

  while (true) {
    const response = await fetchWithRetry(
      `${NOCODB_URL}/api/v2/tables/${tableId}/records?limit=${limit}&offset=${offset}`,
      { headers: { 'xc-token': NOCODB_TOKEN } }
    );

    const json = await response.json();
    const records: T[] = json.list || [];
    allRecords = allRecords.concat(records);

    if (records.length < limit) break;
    offset += limit;
  }

  return allRecords;
}

// Cache keys
const CACHE_KEY = 'hihala_data_cache';
const CACHE_TIMESTAMP_KEY = 'hihala_data_cache_timestamp';
const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days

// Default umrah data (overridden by NocoDB PilgrimStats when available)
export let umrahData: UmrahData = {
  2024: { 1: 15222497, 2: 10521465, 3: 6270868, 4: 7435625 },
  2025: { 1: 15222497, 2: 5443393, 3: 26643148, 4: 31591871 }
};

export async function fetchPilgrimStats(): Promise<UmrahData> {
  try {
    const tables = await discoverTableIds();
    if (!tables['PilgrimStats']) {
      console.warn('PilgrimStats table not found, using defaults');
      return umrahData;
    }
    const url = `${NOCODB_URL}/api/v2/tables/${tables['PilgrimStats']}/records?limit=50`;
    const res = await fetchWithRetry(url, { headers: { 'xc-token': NOCODB_TOKEN } });
    const json = await res.json();
    const records = json.list || [];

    const data: UmrahData = { 2024: {}, 2025: {} };
    for (const r of records) {
      const year = r.Year as number;
      const qStr = r.Quarter as string;
      const qNum = parseInt(qStr.replace('Q', ''));
      const total = r.TotalPilgrims as number;
      if (year && qNum && total) {
        if (!data[year]) data[year] = {};
        data[year][qNum] = total;
      }
    }

    umrahData = data;
    console.log('PilgrimStats loaded from NocoDB:', data);
    return data;
  } catch (err) {
    console.warn('Failed to fetch PilgrimStats, using defaults:', (err as Error).message);
    return umrahData;
  }
}

// ============================================
// NocoDB DailySales Fetching
// ============================================

async function fetchFromNocoDB(): Promise<MuseumRecord[]> {
  console.log('Fetching from NocoDB DailySales...');

  const tables = await discoverTableIds();
  if (!tables['DailySales']) throw new Error("NocoDB table 'DailySales' not found — run ETL sync first");

  const rows = await fetchNocoDBTable<NocoDBDailySale>(tables['DailySales']);

  const data: MuseumRecord[] = rows.map(row => {
    const date = row.Date;
    const year = date ? date.substring(0, 4) : '';
    const month = date ? parseInt(date.substring(5, 7)) : 0;
    const quarter = month <= 3 ? '1' : month <= 6 ? '2' : month <= 9 ? '3' : '4';

    return {
      date,
      district: row.District,
      museum_name: row.MuseumName,
      channel: row.Channel,
      visits: row.Visits,
      tickets: row.Tickets,
      revenue_gross: row.GrossRevenue,
      revenue_net: row.NetRevenue,
      year,
      quarter,
    };
  }).filter(r => r.date && r.museum_name);

  console.log(`Loaded ${data.length} rows from NocoDB DailySales`);
  return data;
}

// ============================================
// Offline Cache Functions
// ============================================

function saveToCache(data: MuseumRecord[]): void {
  try {
    localStorage.setItem(CACHE_KEY, JSON.stringify(data));
    localStorage.setItem(CACHE_TIMESTAMP_KEY, Date.now().toString());
    console.log(`Cached ${data.length} rows to localStorage`);
  } catch (err) {
    console.warn('Failed to save to cache:', (err as Error).message);
  }
}

function loadFromCache(): CacheResult | null {
  try {
    const cached = localStorage.getItem(CACHE_KEY);
    const timestamp = localStorage.getItem(CACHE_TIMESTAMP_KEY);

    if (!cached) return null;

    const data: MuseumRecord[] = JSON.parse(cached);
    const age = timestamp ? Date.now() - parseInt(timestamp) : Infinity;
    const isStale = age > CACHE_MAX_AGE_MS;

    console.log(`Loaded ${data.length} rows from cache (age: ${Math.round(age / 1000 / 60)} min, stale: ${isStale})`);

    return { data, isStale, timestamp: parseInt(timestamp || '0') };
  } catch (err) {
    console.warn('Failed to load from cache:', (err as Error).message);
    return null;
  }
}

export function getCacheStatus(): CacheStatus {
  const timestamp = localStorage.getItem(CACHE_TIMESTAMP_KEY);
  const cached = localStorage.getItem(CACHE_KEY);

  if (!cached || !timestamp) {
    return { available: false, timestamp: null, age: null, rows: 0 };
  }

  const ts = parseInt(timestamp);
  const data: MuseumRecord[] = JSON.parse(cached);

  return {
    available: true,
    timestamp: new Date(ts).toISOString(),
    age: Date.now() - ts,
    rows: data.length,
    isStale: (Date.now() - ts) > CACHE_MAX_AGE_MS
  };
}

export function clearCache(): void {
  localStorage.removeItem(CACHE_KEY);
  localStorage.removeItem(CACHE_TIMESTAMP_KEY);
  console.log('Cache cleared');
}

// ============================================
// Error Classification
// ============================================

function classifyError(err: Error): DataErrorType {
  const msg = err.message.toLowerCase();
  if (msg.includes('not configured')) return 'config';
  if (msg.includes('timed out') || msg.includes('timeout')) return 'timeout';
  if (msg.includes('401') || msg.includes('403') || msg.includes('authentication') || msg.includes('unauthorized')) return 'auth';
  if (msg.includes('failed to fetch') || msg.includes('network') || msg.includes('econnrefused') || msg.includes('err_connection')) return 'network';
  return 'unknown';
}

// ============================================
// Main Data Fetcher (with offline fallback)
// ============================================

export async function fetchData(): Promise<FetchResult> {
  if (!NOCODB_URL || !NOCODB_TOKEN || !NOCODB_BASE_ID) {
    const cached = loadFromCache();
    if (cached) {
      console.warn('NocoDB not configured, using cached data');
      return { data: cached.data, fromCache: true, cacheTimestamp: cached.timestamp };
    }
    throw new DataError('NocoDB not configured', 'config');
  }

  try {
    const data = await fetchFromNocoDB();

    // Suspicious data check — prefer cache if NocoDB returns too few rows
    const cached = loadFromCache();
    if (data.length < 10 && cached && cached.data.length > 10) {
      console.warn('NocoDB returned suspiciously few rows, using cache');
      return { data: cached.data, fromCache: true, cacheTimestamp: cached.timestamp };
    }

    saveToCache(data);
    return { data, fromCache: false };
  } catch (err) {
    console.error('NocoDB fetch failed:', (err as Error).message);

    const cached = loadFromCache();
    if (cached) {
      console.warn(`Using cached data from ${new Date(cached.timestamp).toLocaleString()} (offline mode)`);
      return { data: cached.data, fromCache: true, cacheTimestamp: cached.timestamp };
    }

    const errorType = classifyError(err as Error);
    throw new DataError((err as Error).message, errorType);
  }
}

export async function refreshData(): Promise<FetchResult> {
  if (!NOCODB_URL || !NOCODB_TOKEN || !NOCODB_BASE_ID) {
    throw new DataError('NocoDB not configured', 'config');
  }

  const data = await fetchFromNocoDB();
  saveToCache(data);
  return { data, fromCache: false };
}

// ============================================
// Data Filtering & Metrics
// ============================================

export function filterData(data: MuseumRecord[], filters: Filters): MuseumRecord[] {
  return data.filter(row => {
    if (filters.year && filters.year !== 'all' && row.year !== filters.year) return false;
    if (filters.district && filters.district !== 'all' && row.district !== filters.district) return false;
    if (filters.channel.length > 0 && !filters.channel.includes(row.channel)) return false;
    if (filters.museum.length > 0 && !filters.museum.includes(row.museum_name)) return false;
    if (filters.quarter && filters.quarter !== 'all' && row.quarter !== filters.quarter) return false;
    return true;
  });
}

export function filterDataByDateRange(
  data: MuseumRecord[],
  startDate: string,
  endDate: string,
  filters: Partial<DateRangeFilters> = {}
): MuseumRecord[] {
  return data.filter(row => {
    if (!row.date) return false;
    if (row.date < startDate || row.date > endDate) return false;
    if (filters.district && filters.district !== 'all' && row.district !== filters.district) return false;
    if (filters.channel && filters.channel.length > 0 && !filters.channel.includes(row.channel)) return false;
    if (filters.museum && filters.museum.length > 0 && !filters.museum.includes(row.museum_name)) return false;
    return true;
  });
}

export function calculateMetrics(data: MuseumRecord[], includeVAT: boolean = true): Metrics {
  const revenueField = includeVAT ? 'revenue_gross' : 'revenue_net';
  const revenue = data.reduce((sum, row) => sum + (row[revenueField] || 0), 0);
  const visitors = data.reduce((sum, row) => sum + (row.visits || 0), 0);
  const tickets = data.reduce((sum, row) => sum + (row.tickets || 0), 0);
  const avgRevPerVisitor = visitors > 0 ? revenue / visitors : 0;
  return { revenue, visitors, tickets, avgRevPerVisitor };
}

// ============================================
// Formatting Functions
// ============================================

export function formatCurrency(num: number): string {
  if (isNaN(num)) return 'SAR 0';
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'SAR',
    maximumFractionDigits: 0
  }).format(num);
}

export function formatNumber(num: number): string {
  if (isNaN(num)) return '0';
  return new Intl.NumberFormat('en-US').format(Math.round(num));
}

export function formatCompact(num: number): string {
  if (isNaN(num)) return '0';
  const absNum = Math.abs(num);
  if (absNum >= 1000000) return (num / 1000000).toFixed(1) + 'M';
  if (absNum >= 1000) return (num / 1000).toFixed(0) + 'K';
  return formatNumber(num);
}

export function formatCompactCurrency(num: number): string {
  if (isNaN(num)) return 'SAR 0';
  const absNum = Math.abs(num);
  if (absNum >= 1000000) return 'SAR ' + (num / 1000000).toFixed(1) + 'M';
  if (absNum >= 1000) return 'SAR ' + (num / 1000).toFixed(0) + 'K';
  return formatCurrency(num);
}

// ============================================
// Grouping Functions
// ============================================

export function getWeekStart(dateStr: string): string | null {
  if (!dateStr || !dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) return null;

  const [year, month, day] = dateStr.split('-').map(Number);
  const date = new Date(year, month - 1, day);
  const dayOfWeek = date.getDay();
  const diff = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;

  const monday = new Date(year, month - 1, day + diff);
  const y = monday.getFullYear();
  const m = String(monday.getMonth() + 1).padStart(2, '0');
  const d = String(monday.getDate()).padStart(2, '0');

  return `${y}-${m}-${d}`;
}

export function groupByWeek(data: MuseumRecord[], includeVAT: boolean = true): Record<string, GroupedData> {
  const revenueField = includeVAT ? 'revenue_gross' : 'revenue_net';
  const grouped: Record<string, GroupedData> = {};
  data.forEach(row => {
    if (!row.date) return;
    const weekStart = getWeekStart(row.date);
    if (!weekStart) return;
    if (!grouped[weekStart]) grouped[weekStart] = { revenue: 0, visitors: 0, tickets: 0 };
    grouped[weekStart].revenue += row[revenueField] || 0;
    grouped[weekStart].visitors += row.visits || 0;
    grouped[weekStart].tickets += row.tickets || 0;
  });
  return grouped;
}

export function groupByMuseum(data: MuseumRecord[], includeVAT: boolean = true): Record<string, GroupedData> {
  const revenueField = includeVAT ? 'revenue_gross' : 'revenue_net';
  const grouped: Record<string, GroupedData> = {};
  data.forEach(row => {
    if (!row.museum_name) return;
    if (!grouped[row.museum_name]) grouped[row.museum_name] = { revenue: 0, visitors: 0, tickets: 0 };
    grouped[row.museum_name].revenue += row[revenueField] || 0;
    grouped[row.museum_name].visitors += row.visits || 0;
    grouped[row.museum_name].tickets += row.tickets || 0;
  });
  return grouped;
}

export function groupByChannel(data: MuseumRecord[], includeVAT: boolean = true): Record<string, GroupedData> {
  const revenueField = includeVAT ? 'revenue_gross' : 'revenue_net';
  const grouped: Record<string, GroupedData> = {};
  data.forEach(row => {
    if (!row.channel) return;
    if (!grouped[row.channel]) grouped[row.channel] = { revenue: 0, visitors: 0, tickets: 0 };
    grouped[row.channel].revenue += row[revenueField] || 0;
    grouped[row.channel].visitors += row.visits || 0;
    grouped[row.channel].tickets += row.tickets || 0;
  });
  return grouped;
}

// ============================================
// Data Extraction Helpers
// ============================================

export function getUniqueYears(data: MuseumRecord[]): string[] {
  const years = [...new Set(data.map(r => r.year).filter(Boolean))];
  return years.sort((a, b) => parseInt(a) - parseInt(b));
}

export function getUniqueDistricts(data: MuseumRecord[]): string[] {
  return [...new Set(data.map(r => r.district).filter(Boolean))].sort();
}

export function groupByDistrict(data: MuseumRecord[], includeVAT: boolean = true): Record<string, GroupedData> {
  const revenueField = includeVAT ? 'revenue_gross' : 'revenue_net';
  const grouped: Record<string, GroupedData> = {};
  data.forEach(row => {
    if (!row.district) return;
    if (!grouped[row.district]) grouped[row.district] = { revenue: 0, visitors: 0, tickets: 0 };
    grouped[row.district].revenue += row[revenueField] || 0;
    grouped[row.district].visitors += row.visits || 0;
    grouped[row.district].tickets += row.tickets || 0;
  });
  return grouped;
}

export function getMuseumsForDistrict(data: MuseumRecord[], district: string): string[] {
  if (district === 'all') return getUniqueMuseums(data);
  return [...new Set(data.filter(r => r.district === district).map(r => r.museum_name).filter(Boolean))].sort();
}

export function getUniqueChannels(data: MuseumRecord[]): string[] {
  return [...new Set(data.map(r => r.channel).filter(Boolean))].sort();
}

export function getUniqueMuseums(data: MuseumRecord[]): string[] {
  return [...new Set(data.map(r => r.museum_name).filter(Boolean))].sort();
}

export function getLatestYear(data: MuseumRecord[]): string {
  const years = getUniqueYears(data);
  return years.length > 0 ? years[years.length - 1] : '2025';
}
