import { Injectable, Logger } from "@nestjs/common";
import { Cron } from "@nestjs/schedule";
import { Prisma } from "@prisma/client";
import { PrismaService } from "../database/prisma.service";
import { TaskLockService } from "./task-lock.service";

/**
 * Ensures every FT football match has a football_ai_features row so the
 * Python ai-engine does not fall back to live inference (which triggers the
 * `ai_features_inferred_from_history` -18 penalty in betting_brain).
 *
 * Heavy enrichment (h2h, referee, possession averages, etc.) is handled by the
 * separate `ai-engine/scripts/enrich_ai_features.py` script; this task only
 * guarantees row existence with ELO + form populated from authoritative
 * sources. Default values for the rest are taken from the schema.
 */
@Injectable()
export class FeatureEnrichmentTask {
  private readonly logger = new Logger(FeatureEnrichmentTask.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly taskLock: TaskLockService,
  ) {}

  // Runs between historical-results-sync (08:00) and prediction-settlement (08:30)
  @Cron("15 8 * * *", { timeZone: "Europe/Istanbul" })
  async ensureFeatureRows() {
    if (process.env.FEEDER_MODE === "historical") {
      this.logger.debug("Skipping feature enrichment in historical feeder mode");
      return;
    }
    await this.taskLock.runWithLease(
      "ensureFeatureRows",
      60 * 60 * 1000,
      () => this.runEnrichment(),
      this.logger,
    );
  }

  async runEnrichment(): Promise<{ inserted: number; repaired: number; movementUpdated: number }> {
    // One-time reset: previous runs applied a non-idempotent 0-45→0-100
    // multiplier; some rows hit it twice and ended up over-amplified. Delete
    // task_v1 rows so they get re-inserted from scratch by the next step.
    // This block can be removed after one successful production run.
    const resetRows = await this.prisma.$queryRaw<{ deleted: bigint }[]>(Prisma.sql`
      WITH d AS (
        DELETE FROM football_ai_features
        WHERE calculator_ver = 'feature_enrichment_task_v1'
        RETURNING match_id
      )
      SELECT COUNT(*)::bigint AS deleted FROM d
    `);
    const repaired = Number(resetRows[0]?.deleted ?? 0);

    const rows = await this.prisma.$queryRaw<{ inserted: bigint }[]>(Prisma.sql`
      WITH form_calc AS (
        SELECT
          team_id,
          -- Normalize 0-45 raw score (30 win + 15 max goal-bonus per match) to 0-100
          LEAST(100.0,
            (AVG(CASE
              WHEN score_for > score_against THEN 30
              WHEN score_for = score_against THEN 10
              ELSE 0
            END) + LEAST(AVG(score_for) * 5, 15)) * (100.0 / 45.0)
          ) AS form_score
        FROM (
          SELECT m.home_team_id AS team_id, m.score_home AS score_for,
                 m.score_away AS score_against, true AS is_home,
                 ROW_NUMBER() OVER (PARTITION BY m.home_team_id ORDER BY m.mst_utc DESC) AS rn
          FROM matches m
          WHERE m.status='FT' AND m.score_home IS NOT NULL AND m.sport='football'
          UNION ALL
          SELECT m.away_team_id, m.score_away, m.score_home, false,
                 ROW_NUMBER() OVER (PARTITION BY m.away_team_id ORDER BY m.mst_utc DESC)
          FROM matches m
          WHERE m.status='FT' AND m.score_home IS NOT NULL AND m.sport='football'
        ) recent
        WHERE rn <= 5
        GROUP BY team_id
      ),
      missing AS (
        SELECT m.id, m.home_team_id, m.away_team_id
        FROM matches m
        LEFT JOIN football_ai_features f ON f.match_id = m.id
        WHERE m.sport='football'
          AND m.status='FT'
          AND m.score_home IS NOT NULL
          AND f.match_id IS NULL
          AND to_timestamp(m.mst_utc/1000) > now() - interval '60 days'
        LIMIT 5000
      ),
      inserted AS (
        INSERT INTO football_ai_features (
          match_id, home_elo, away_elo,
          home_form_score, away_form_score,
          calculator_ver, updated_at
        )
        SELECT
          mi.id,
          COALESCE(eh.overall_elo, 1500.0),
          COALESCE(ea.overall_elo, 1500.0),
          COALESCE(fh.form_score, 50.0),
          COALESCE(fa.form_score, 50.0),
          'feature_enrichment_task_v1',
          NOW()
        FROM missing mi
        LEFT JOIN team_elo_ratings eh ON eh.team_id = mi.home_team_id
        LEFT JOIN team_elo_ratings ea ON ea.team_id = mi.away_team_id
        LEFT JOIN form_calc fh ON fh.team_id = mi.home_team_id
        LEFT JOIN form_calc fa ON fa.team_id = mi.away_team_id
        ON CONFLICT (match_id) DO NOTHING
        RETURNING match_id
      )
      SELECT COUNT(*)::bigint AS inserted FROM inserted
    `);

    const inserted = Number(rows[0]?.inserted ?? 0);

    // Step 3: backfill odds_movement_* columns from odd_selections.opening_value
    // (settlement task's computeMovementForMatch relies on odds_history which is
    // empty in production; bypass it by reading directly from odd_selections).
    const movementRows = await this.prisma.$queryRaw<{ updated: bigint }[]>(Prisma.sql`
      WITH movement AS (
        SELECT
          oc.match_id,
          MAX(CASE WHEN LOWER(oc.name) IN ('maç sonucu','mac sonucu','ms') AND os.name='1'
                   THEN (os.odd_value::float - os.opening_value::float) / NULLIF(os.opening_value::float,0) * 100 END) AS mv_home,
          MAX(CASE WHEN LOWER(oc.name) IN ('maç sonucu','mac sonucu','ms') AND os.name IN ('X','0')
                   THEN (os.odd_value::float - os.opening_value::float) / NULLIF(os.opening_value::float,0) * 100 END) AS mv_draw,
          MAX(CASE WHEN LOWER(oc.name) IN ('maç sonucu','mac sonucu','ms') AND os.name='2'
                   THEN (os.odd_value::float - os.opening_value::float) / NULLIF(os.opening_value::float,0) * 100 END) AS mv_away,
          MAX(CASE WHEN (LOWER(oc.name) LIKE '%2,5%' OR LOWER(oc.name) LIKE '%2.5%')
                    AND (LOWER(os.name) LIKE '%üst%' OR LOWER(os.name) LIKE '%ust%' OR LOWER(os.name) LIKE '%over%')
                   THEN (os.odd_value::float - os.opening_value::float) / NULLIF(os.opening_value::float,0) * 100 END) AS mv_o25,
          MAX(CASE WHEN (LOWER(oc.name) LIKE '%karşılıklı%' OR LOWER(oc.name) LIKE '%karsilikli%' OR LOWER(oc.name)='kg')
                    AND (LOWER(os.name) IN ('var','yes'))
                   THEN (os.odd_value::float - os.opening_value::float) / NULLIF(os.opening_value::float,0) * 100 END) AS mv_btts
        FROM odd_selections os
        JOIN odd_categories oc ON oc.db_id = os.odd_category_db_id
        WHERE os.opening_value IS NOT NULL
          AND os.odd_value IS NOT NULL
          AND os.opening_value::float > 0
        GROUP BY oc.match_id
      ),
      upd AS (
        UPDATE football_ai_features f
        SET odds_movement_home = m.mv_home,
            odds_movement_draw = m.mv_draw,
            odds_movement_away = m.mv_away,
            odds_movement_o25 = m.mv_o25,
            odds_movement_btts = m.mv_btts,
            odds_sharpness = (
              COALESCE(ABS(m.mv_home),0) + COALESCE(ABS(m.mv_draw),0) +
              COALESCE(ABS(m.mv_away),0) + COALESCE(ABS(m.mv_o25),0) +
              COALESCE(ABS(m.mv_btts),0)
            ) / NULLIF(
              (CASE WHEN m.mv_home IS NOT NULL THEN 1 ELSE 0 END) +
              (CASE WHEN m.mv_draw IS NOT NULL THEN 1 ELSE 0 END) +
              (CASE WHEN m.mv_away IS NOT NULL THEN 1 ELSE 0 END) +
              (CASE WHEN m.mv_o25  IS NOT NULL THEN 1 ELSE 0 END) +
              (CASE WHEN m.mv_btts IS NOT NULL THEN 1 ELSE 0 END), 0)
        FROM movement m
        WHERE f.match_id = m.match_id
          AND (f.odds_movement_home IS NULL OR f.odds_movement_home = 0)
        RETURNING f.match_id
      )
      SELECT COUNT(*)::bigint AS updated FROM upd
    `);
    const movementUpdated = Number(movementRows[0]?.updated ?? 0);

    this.logger.log(
      `Feature enrichment finished: inserted=${inserted} repaired=${repaired} movement=${movementUpdated}`,
    );
    return { inserted, repaired, movementUpdated };
  }
}
