// ABOUTME: Data quality metrics for races, meetings, and results
// ABOUTME: Tracks completeness and health of scraped racing data

import { PrismaClient } from '@prisma/client';
import { Gauge } from 'prom-client';
import logger from '../utils/logger';

// ============================================================================
// Data Quality Metrics
// ============================================================================

export const racesMissingRunners = new Gauge({
  name: 'races_missing_runners_total',
  help: 'Number of races with no runner data',
});

export const racesMissingResults = new Gauge({
  name: 'races_missing_results_total',
  help: 'Number of final races without results',
  labelNames: ['minutes_after_start'],
});

export const oddsSnapshotsMissing = new Gauge({
  name: 'odds_snapshots_missing_total',
  help: 'Number of runners missing odds snapshots',
  labelNames: ['snapshot_type'],
});

export const meetingsActive = new Gauge({
  name: 'meetings_active_total',
  help: 'Number of active meetings today',
  labelNames: ['country', 'category'],
});

export const racesByStatus = new Gauge({
  name: 'races_by_status_total',
  help: 'Number of races by status',
  labelNames: ['status'],
});

export const runnersScratched = new Gauge({
  name: 'runners_scratched_total',
  help: 'Number of scratched runners by timing',
  labelNames: ['timing'],
});

export const racesTodayTotal = new Gauge({
  name: 'races_today_total',
  help: 'Total number of races scheduled for today',
  labelNames: ['country', 'category'],
});

export const runnersPerRaceAvg = new Gauge({
  name: 'runners_per_race_average',
  help: 'Average number of runners per race',
  labelNames: ['country', 'category'],
});

export const raceDataFreshness = new Gauge({
  name: 'race_data_freshness_seconds',
  help: 'Age of race data in seconds since last scrape',
  labelNames: ['race_id', 'country', 'category', 'freshness_status'],
});

// ============================================================================
// Data Quality Collector
// ============================================================================

export class DataQualityCollector {
  constructor(private prisma: PrismaClient) {
    logger.info('DataQualityCollector initialized');
  }

  /**
   * Collect all data quality metrics
   */
  async collect(): Promise<void> {
    const startTime = Date.now();

    try {
      await Promise.all([
        this.collectRacesMissingRunners(),
        this.collectRacesMissingResults(),
        this.collectOddsSnapshotsMissing(),
        this.collectMeetingsActive(),
        this.collectRacesByStatus(),
        this.collectRunnersScratched(),
        this.collectRacesToday(),
        this.collectRunnersPerRace(),
        this.collectRaceDataFreshness(),
      ]);

      const duration = Date.now() - startTime;
      logger.debug({ duration }, 'Data quality metrics collected');
    } catch (error) {
      logger.error({
        error: error instanceof Error ? error.message : 'Unknown error',
      }, 'Failed to collect data quality metrics');
    }
  }

  /**
   * Collect races with no runners
   */
  private async collectRacesMissingRunners(): Promise<void> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const count = await this.prisma.race.count({
      where: {
        meeting: {
          date: today,
        },
        runners: {
          none: {},
        },
      },
    });

    racesMissingRunners.set(count);
  }

  /**
   * Collect final races without results
   */
  private async collectRacesMissingResults(): Promise<void> {
    const now = new Date();

    // Check races at different time intervals after start
    const intervals = [
      { minutes: 15, label: '15' },
      { minutes: 30, label: '30' },
      { minutes: 60, label: '60' },
    ];

    for (const interval of intervals) {
      const cutoffTime = new Date(now.getTime() - interval.minutes * 60 * 1000);

      const count = await this.prisma.race.count({
        where: {
          startTime: {
            lte: cutoffTime,
          },
          status: 'Final',
          results: {
            none: {},
          },
        },
      });

      racesMissingResults.labels(interval.label).set(count);
    }
  }

  /**
   * Collect missing odds snapshots
   */
  private async collectOddsSnapshotsMissing(): Promise<void> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const snapshotTypes = ['morning', 't60', 't15', 'final'] as const;

    for (const snapshotType of snapshotTypes) {
      // Count non-scratched runners missing this snapshot type
      const count = await this.prisma.runner.count({
        where: {
          race: {
            meeting: {
              date: today,
            },
          },
          scratched: false,
          oddsSnapshots: {
            none: {
              snapshotType,
            },
          },
        },
      });

      oddsSnapshotsMissing.labels(snapshotType).set(count);
    }
  }

  /**
   * Collect active meetings by country and category
   */
  private async collectMeetingsActive(): Promise<void> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const meetings = await this.prisma.meeting.groupBy({
      by: ['country', 'category'],
      where: {
        date: today,
      },
      _count: true,
    });

    // Reset all combinations first
    const countries = ['AUS', 'NZL'];
    const categories = ['T', 'H'];

    for (const country of countries) {
      for (const category of categories) {
        meetingsActive.labels(country, category).set(0);
      }
    }

    // Set actual counts
    for (const meeting of meetings) {
      meetingsActive.labels(meeting.country, meeting.category).set(meeting._count);
    }
  }

  /**
   * Collect races by status
   */
  private async collectRacesByStatus(): Promise<void> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const statuses = await this.prisma.race.groupBy({
      by: ['status'],
      where: {
        meeting: {
          date: today,
        },
      },
      _count: true,
    });

    // Reset common statuses
    const commonStatuses = ['Open', 'Closed', 'Interim', 'Final', 'Abandoned'];
    for (const status of commonStatuses) {
      racesByStatus.labels(status).set(0);
    }

    // Set actual counts
    for (const statusGroup of statuses) {
      racesByStatus.labels(statusGroup.status).set(statusGroup._count);
    }
  }

  /**
   * Collect scratched runners by timing
   */
  private async collectRunnersScratched(): Promise<void> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);

    // Get all scratched runners for today
    const scratchedRunners = await this.prisma.runner.findMany({
      where: {
        race: {
          meeting: {
            date: today,
          },
        },
        scratched: true,
        scratchedAt: {
          not: null,
        },
      },
      include: {
        race: {
          select: {
            startTime: true,
          },
        },
      },
    });

    // Categorize by timing relative to race start
    let beforeT60 = 0;
    let t60ToT15 = 0;
    let afterT15 = 0;

    for (const runner of scratchedRunners) {
      if (!runner.scratchedAt) continue;

      const minutesBeforeRace =
        (runner.race.startTime.getTime() - runner.scratchedAt.getTime()) / (1000 * 60);

      if (minutesBeforeRace > 60) {
        beforeT60++;
      } else if (minutesBeforeRace > 15) {
        t60ToT15++;
      } else {
        afterT15++;
      }
    }

    runnersScratched.labels('before_t60').set(beforeT60);
    runnersScratched.labels('t60_to_t15').set(t60ToT15);
    runnersScratched.labels('after_t15').set(afterT15);
  }

  /**
   * Collect total races today by country and category
   */
  private async collectRacesToday(): Promise<void> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    // Reset all combinations
    const countries = ['AUS', 'NZL'];
    const categories = ['T', 'H'];

    for (const country of countries) {
      for (const category of categories) {
        racesTodayTotal.labels(country, category).set(0);
      }
    }

    // Query races with meeting data
    const racesWithMeeting = await this.prisma.race.findMany({
      where: {
        meeting: {
          date: today,
        },
      },
      select: {
        country: true,
        meeting: {
          select: {
            category: true,
          },
        },
      },
    });

    const countMap = new Map<string, number>();
    for (const race of racesWithMeeting) {
      const key = `${race.country}_${race.meeting.category}`;
      countMap.set(key, (countMap.get(key) || 0) + 1);
    }

    for (const [key, count] of countMap) {
      const [country, category] = key.split('_');
      racesTodayTotal.labels(country, category).set(count);
    }
  }

  /**
   * Collect average runners per race
   */
  private async collectRunnersPerRace(): Promise<void> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const races = await this.prisma.race.findMany({
      where: {
        meeting: {
          date: today,
        },
      },
      select: {
        country: true,
        meeting: {
          select: {
            category: true,
          },
        },
        _count: {
          select: {
            runners: true,
          },
        },
      },
    });

    // Calculate averages by country and category
    const stats = new Map<string, { total: number; count: number }>();

    for (const race of races) {
      const key = `${race.country}_${race.meeting.category}`;
      const current = stats.get(key) || { total: 0, count: 0 };
      current.total += race._count.runners;
      current.count += 1;
      stats.set(key, current);
    }

    // Reset all combinations
    const countries = ['AUS', 'NZL'];
    const categories = ['T', 'H'];

    for (const country of countries) {
      for (const category of categories) {
        runnersPerRaceAvg.labels(country, category).set(0);
      }
    }

    // Set averages
    for (const [key, stat] of stats) {
      const [country, category] = key.split('_');
      const avg = stat.count > 0 ? stat.total / stat.count : 0;
      runnersPerRaceAvg.labels(country, category).set(avg);
    }
  }

  /**
   * Collect race data freshness (age since last scrape)
   */
  private async collectRaceDataFreshness(): Promise<void> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);

    const now = new Date();

    // Get all races for today with their last scrape time
    const races = await this.prisma.race.findMany({
      where: {
        meeting: {
          date: today,
        },
      },
      select: {
        id: true,
        country: true,
        meeting: {
          select: {
            category: true,
          },
        },
        scrapes: {
          orderBy: {
            scrapedAt: 'desc',
          },
          take: 1,
          select: {
            scrapedAt: true,
          },
        },
      },
    });

    // Clear existing metrics first
    raceDataFreshness.reset();

    // Set freshness metric for each race
    for (const race of races) {
      const lastScrape = race.scrapes[0];

      if (lastScrape) {
        const ageSeconds = Math.round((now.getTime() - lastScrape.scrapedAt.getTime()) / 1000);
        const ageMinutes = Math.round(ageSeconds / 60);

        // Determine freshness status
        let freshnessStatus: string;
        if (ageMinutes <= 15) {
          freshnessStatus = 'fresh';
        } else if (ageMinutes <= 60) {
          freshnessStatus = 'stale';
        } else {
          freshnessStatus = 'very_stale';
        }

        raceDataFreshness
          .labels(race.id, race.country, race.meeting.category, freshnessStatus)
          .set(ageSeconds);
      } else {
        // No scrapes yet - set to a very high value to indicate missing data
        raceDataFreshness
          .labels(race.id, race.country, race.meeting.category, 'missing')
          .set(999999);
      }
    }
  }
}
