import { Injectable, Logger } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { PrismaService } from "../../../database/prisma.service";

// ─────────────────────────────────────────────────────────────
// Types
// ─────────────────────────────────────────────────────────────

export interface FrequencySignal {
  market: string;
  pick: string;
  homeSignal: number;
  awaySignal: number;
  combinedSignal: number;
  homeMatchCount: number;
  awayMatchCount: number;
  leagueBonus: number;
  confidence: number;
}

export interface MatchCandidate {
  matchId: string;
  homeTeamId: string;
  awayTeamId: string;
  homeTeamName: string;
  awayTeamName: string;
  leagueId: string;
  leagueName: string;
  homeOdds: number;
  awayOdds: number;
  drawOdds: number;
  signals: FrequencySignal[];
  bestSignal: FrequencySignal | null;
  matchTime: number;
}

interface TeamFrequencyRow {
  team_id: string;
  venue: "home" | "away";
  odds_band: string;
  total_matches: number;
  ou15_rate: number;
  ou25_rate: number;
  ou35_rate: number;
  btts_rate: number;
  win_rate: number;
  avg_goals: number;
}

interface LeagueProfileRow {
  league_id: string;
  league_name: string;
  total_matches: number;
  ou25_rate: number;
  btts_rate: number;
  avg_goals: number;
}

interface UpcomingMatchRow {
  match_id: string;
  home_team_id: string;
  away_team_id: string;
  home_team_name: string;
  away_team_name: string;
  league_id: string;
  league_name: string;
  mst_utc: bigint;
  ms1_odds: number | null;
  ms2_odds: number | null;
  msx_odds: number | null;
  ou25_over_odds: number | null;
  ou25_under_odds: number | null;
  btts_yes_odds: number | null;
  btts_no_odds: number | null;
  ou15_over_odds: number | null;
  ou35_over_odds: number | null;
}

// ─────────────────────────────────────────────────────────────
// Constants
// ─────────────────────────────────────────────────────────────

const MIN_MATCHES = 3;

// const GOLCU_LEAGUES = new Set([
//   // Strategy generator'dan türetilen yüksek golcü ligler
//   // Lig isimleri veritabanındaki gibi
// ]);

// const DEFANSIF_LEAGUES = new Set([
//   // Düşük golcü ligler
// ]);

// ─────────────────────────────────────────────────────────────
// Service
// ─────────────────────────────────────────────────────────────

@Injectable()
export class FrequencyEngineService {
  private readonly logger = new Logger(FrequencyEngineService.name);

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Belirli bir takımın ev/deplasman + oran bandı koşullu frekanslarını döndürür.
   */
  async getTeamFrequency(
    teamId: string,
    venue: "home" | "away",
    oddsBand: string,
  ): Promise<TeamFrequencyRow | null> {
    // venue is a typed literal ("home"|"away") — safe to use with Prisma.raw()
    const venueColumnRaw = Prisma.raw(
      venue === "home" ? "m.home_team_id" : "m.away_team_id",
    );
    const oddsSelectionValue = venue === "home" ? "1" : "2";
    const winConditionRaw = Prisma.raw(
      venue === "home" ? "score_home > score_away" : "score_away > score_home",
    );
    const bandRange = this.parseBandRange(oddsBand);

    if (!bandRange) {
      return null;
    }

    const rows = await this.prisma.$queryRaw<TeamFrequencyRow[]>(Prisma.sql`
      WITH team_matches AS (
        SELECT
          m.id AS match_id,
          m.score_home,
          m.score_away,
          (m.score_home + m.score_away) AS total_goals,
          CAST(os.odd_value AS DECIMAL) AS team_odds
        FROM matches m
        JOIN odd_categories oc ON oc.match_id = m.id AND oc.name = 'Maç Sonucu'
        JOIN odd_selections os ON os.odd_category_db_id = oc.db_id AND os.name = ${oddsSelectionValue}
        WHERE m.status = 'FT'
          AND m.score_home IS NOT NULL
          AND ${venueColumnRaw} = ${teamId}
          AND CAST(os.odd_value AS DECIMAL) >= ${bandRange.min}
          AND CAST(os.odd_value AS DECIMAL) < ${bandRange.max}
      )
      SELECT
        ${teamId}::text AS team_id,
        ${venue}::text AS venue,
        ${oddsBand}::text AS odds_band,
        COUNT(*)::int AS total_matches,
        COALESCE(AVG(CASE WHEN total_goals > 1 THEN 1.0 ELSE 0.0 END), 0)::float AS ou15_rate,
        COALESCE(AVG(CASE WHEN total_goals > 2 THEN 1.0 ELSE 0.0 END), 0)::float AS ou25_rate,
        COALESCE(AVG(CASE WHEN total_goals > 3 THEN 1.0 ELSE 0.0 END), 0)::float AS ou35_rate,
        COALESCE(AVG(CASE WHEN score_home > 0 AND score_away > 0 THEN 1.0 ELSE 0.0 END), 0)::float AS btts_rate,
        COALESCE(AVG(CASE WHEN ${winConditionRaw} THEN 1.0 ELSE 0.0 END), 0)::float AS win_rate,
        COALESCE(AVG(total_goals), 0)::float AS avg_goals
      FROM team_matches
    `);

    if (!rows.length || rows[0].total_matches < MIN_MATCHES) {
      return null;
    }

    return rows[0];
  }

  /**
   * İki takımın oran bandı geçmişlerini çapraz kontrol eder.
   * Tüm marketler için kombine sinyal üretir.
   */
  async getMatchFrequencySignals(
    homeTeamId: string,
    awayTeamId: string,
    homeOdds: number,
    awayOdds: number,
    leagueId?: string,
  ): Promise<FrequencySignal[]> {
    const homeBand = this.getOddsBand(homeOdds);
    const awayBand = this.getOddsBand(awayOdds);

    const [homeFreq, awayFreq, leagueProfile] = await Promise.all([
      this.getTeamFrequency(homeTeamId, "home", homeBand),
      this.getTeamFrequency(awayTeamId, "away", awayBand),
      leagueId ? this.getLeagueProfile(leagueId) : null,
    ]);

    if (!homeFreq || !awayFreq) {
      return [];
    }

    const leagueBonus = this.calculateLeagueBonus(leagueProfile);
    const signals: FrequencySignal[] = [];

    // OU 1.5 OVER
    const ou15Combined = (homeFreq.ou15_rate + awayFreq.ou15_rate) / 2;
    if (ou15Combined >= 0.8) {
      signals.push({
        market: "OU1.5_OVER",
        pick: "1.5 UST",
        homeSignal: homeFreq.ou15_rate,
        awaySignal: awayFreq.ou15_rate,
        combinedSignal: ou15Combined,
        homeMatchCount: homeFreq.total_matches,
        awayMatchCount: awayFreq.total_matches,
        leagueBonus,
        confidence: this.calculateConfidence(
          ou15Combined,
          homeFreq.total_matches,
          awayFreq.total_matches,
          leagueBonus,
        ),
      });
    }

    // OU 2.5 OVER
    const ou25Combined = (homeFreq.ou25_rate + awayFreq.ou25_rate) / 2;
    if (ou25Combined >= 0.6) {
      signals.push({
        market: "OU2.5_OVER",
        pick: "2.5 UST",
        homeSignal: homeFreq.ou25_rate,
        awaySignal: awayFreq.ou25_rate,
        combinedSignal: ou25Combined,
        homeMatchCount: homeFreq.total_matches,
        awayMatchCount: awayFreq.total_matches,
        leagueBonus,
        confidence: this.calculateConfidence(
          ou25Combined,
          homeFreq.total_matches,
          awayFreq.total_matches,
          leagueBonus,
        ),
      });
    }

    // OU 3.5 OVER
    const ou35Combined = (homeFreq.ou35_rate + awayFreq.ou35_rate) / 2;
    if (ou35Combined >= 0.5) {
      signals.push({
        market: "OU3.5_OVER",
        pick: "3.5 UST",
        homeSignal: homeFreq.ou35_rate,
        awaySignal: awayFreq.ou35_rate,
        combinedSignal: ou35Combined,
        homeMatchCount: homeFreq.total_matches,
        awayMatchCount: awayFreq.total_matches,
        leagueBonus,
        confidence: this.calculateConfidence(
          ou35Combined,
          homeFreq.total_matches,
          awayFreq.total_matches,
          leagueBonus,
        ),
      });
    }

    // BTTS YES
    const bttsCombined = (homeFreq.btts_rate + awayFreq.btts_rate) / 2;
    if (bttsCombined >= 0.6) {
      signals.push({
        market: "BTTS_YES",
        pick: "KG VAR",
        homeSignal: homeFreq.btts_rate,
        awaySignal: awayFreq.btts_rate,
        combinedSignal: bttsCombined,
        homeMatchCount: homeFreq.total_matches,
        awayMatchCount: awayFreq.total_matches,
        leagueBonus,
        confidence: this.calculateConfidence(
          bttsCombined,
          homeFreq.total_matches,
          awayFreq.total_matches,
          leagueBonus,
        ),
      });
    }

    // OU 2.5 UNDER (düşük gol beklentisi)
    const ou25UnderCombined =
      (1 - homeFreq.ou25_rate + (1 - awayFreq.ou25_rate)) / 2;
    if (ou25UnderCombined >= 0.65) {
      signals.push({
        market: "OU2.5_UNDER",
        pick: "2.5 ALT",
        homeSignal: 1 - homeFreq.ou25_rate,
        awaySignal: 1 - awayFreq.ou25_rate,
        combinedSignal: ou25UnderCombined,
        homeMatchCount: homeFreq.total_matches,
        awayMatchCount: awayFreq.total_matches,
        leagueBonus: -leagueBonus, // golcü lig bonusu ters çevrilir
        confidence: this.calculateConfidence(
          ou25UnderCombined,
          homeFreq.total_matches,
          awayFreq.total_matches,
          -leagueBonus,
        ),
      });
    }

    // MS HOME WIN (ev sahibi kazanma)
    const hwCombined = (homeFreq.win_rate + awayFreq.win_rate) / 2;
    // awayFreq.win_rate aslında deplasman takımının KAYBETme oranı
    // (away takımı o bandda maçları kazanma değil, kaybetme olarak bak)
    if (hwCombined >= 0.7 && homeOdds > 1.1 && homeOdds < 3.5) {
      signals.push({
        market: "MS_HOME",
        pick: "MS 1",
        homeSignal: homeFreq.win_rate,
        awaySignal: awayFreq.win_rate,
        combinedSignal: hwCombined,
        homeMatchCount: homeFreq.total_matches,
        awayMatchCount: awayFreq.total_matches,
        leagueBonus: 0,
        confidence: this.calculateConfidence(
          hwCombined,
          homeFreq.total_matches,
          awayFreq.total_matches,
          0,
        ),
      });
    }

    // Güvene göre sırala (en güçlü sinyal önce)
    signals.sort((a, b) => b.confidence - a.confidence);

    return signals;
  }

  /**
   * Yaklaşan maçları oranlarıyla birlikte getirir.
   * LiveMatch tablosundan JSON odds parse eder.
   */
  async getUpcomingMatchesWithOdds(
    matchIds?: string[],
    limit: number = 50,
  ): Promise<UpcomingMatchRow[]> {
    const nowMs = Date.now();

    if (matchIds && matchIds.length > 0) {
      // Belirli maçlar istendi
      return this.prisma.$queryRaw<UpcomingMatchRow[]>(Prisma.sql`
        SELECT
          lm.id AS match_id,
          lm.home_team_id,
          lm.away_team_id,
          COALESCE(ht.name, 'Unknown') AS home_team_name,
          COALESCE(at.name, 'Unknown') AS away_team_name,
          COALESCE(lm.league_id, '') AS league_id,
          COALESCE(l.name, 'Unknown') AS league_name,
          lm.mst_utc,
          (lm.odds->'Maç Sonucu'->>'1')::decimal AS ms1_odds,
          (lm.odds->'Maç Sonucu'->>'2')::decimal AS ms2_odds,
          (lm.odds->'Maç Sonucu'->>'0')::decimal AS msx_odds,
          (lm.odds->'2,5 Alt/Üst'->>'Üst')::decimal AS ou25_over_odds,
          (lm.odds->'2,5 Alt/Üst'->>'Alt')::decimal AS ou25_under_odds,
          (lm.odds->'Karşılıklı Gol'->>'Var')::decimal AS btts_yes_odds,
          (lm.odds->'Karşılıklı Gol'->>'Yok')::decimal AS btts_no_odds,
          (lm.odds->'1,5 Alt/Üst'->>'Üst')::decimal AS ou15_over_odds,
          (lm.odds->'3,5 Alt/Üst'->>'Üst')::decimal AS ou35_over_odds
        FROM live_matches lm
        LEFT JOIN teams ht ON lm.home_team_id = ht.id
        LEFT JOIN teams at ON lm.away_team_id = at.id
        LEFT JOIN leagues l ON lm.league_id = l.id
        WHERE lm.id = ANY(${matchIds})
          AND lm.odds IS NOT NULL
          AND lm.odds != 'null'::jsonb
        ORDER BY lm.mst_utc ASC
      `);
    }

    // Otomatik: yaklaşan tüm maçlar
    return this.prisma.$queryRaw<UpcomingMatchRow[]>(Prisma.sql`
      SELECT
        lm.id AS match_id,
        lm.home_team_id,
        lm.away_team_id,
        COALESCE(ht.name, 'Unknown') AS home_team_name,
        COALESCE(at.name, 'Unknown') AS away_team_name,
        COALESCE(lm.league_id, '') AS league_id,
        COALESCE(l.name, 'Unknown') AS league_name,
        lm.mst_utc,
        (lm.odds->'Maç Sonucu'->>'1')::decimal AS ms1_odds,
        (lm.odds->'Maç Sonucu'->>'2')::decimal AS ms2_odds,
        (lm.odds->'Maç Sonucu'->>'0')::decimal AS msx_odds,
        (lm.odds->'2,5 Alt/Üst'->>'Üst')::decimal AS ou25_over_odds,
        (lm.odds->'2,5 Alt/Üst'->>'Alt')::decimal AS ou25_under_odds,
        (lm.odds->'Karşılıklı Gol'->>'Var')::decimal AS btts_yes_odds,
        (lm.odds->'Karşılıklı Gol'->>'Yok')::decimal AS btts_no_odds,
        (lm.odds->'1,5 Alt/Üst'->>'Üst')::decimal AS ou15_over_odds,
        (lm.odds->'3,5 Alt/Üst'->>'Üst')::decimal AS ou35_over_odds
      FROM live_matches lm
      LEFT JOIN teams ht ON lm.home_team_id = ht.id
      LEFT JOIN teams at ON lm.away_team_id = at.id
      LEFT JOIN leagues l ON lm.league_id = l.id
      WHERE lm.mst_utc >= ${BigInt(nowMs)}
        AND lm.sport = 'football'
        AND lm.odds IS NOT NULL
        AND lm.odds != 'null'::jsonb
        AND (lm.status IS NULL OR lm.status NOT IN ('FT', 'AET', 'PEN', 'ABD', 'CANC', 'PST', 'SUSP', 'INT', 'AWD', 'WO'))
        AND (lm.state IS NULL OR lm.state NOT IN ('after', 'postponed', 'cancelled', 'abandoned'))
      ORDER BY lm.mst_utc ASC
      LIMIT ${limit}
    `);
  }

  /**
   * Lig bazlı gol profili.
   */
  async getLeagueProfile(leagueId: string): Promise<LeagueProfileRow | null> {
    const rows = await this.prisma.$queryRaw<LeagueProfileRow[]>(Prisma.sql`
      SELECT
        m.league_id,
        l.name AS league_name,
        COUNT(*)::int AS total_matches,
        AVG(CASE WHEN (m.score_home + m.score_away) > 2 THEN 1.0 ELSE 0.0 END)::float AS ou25_rate,
        AVG(CASE WHEN m.score_home > 0 AND m.score_away > 0 THEN 1.0 ELSE 0.0 END)::float AS btts_rate,
        AVG(m.score_home + m.score_away)::float AS avg_goals
      FROM matches m
      JOIN leagues l ON m.league_id = l.id
      WHERE m.status = 'FT'
        AND m.score_home IS NOT NULL
        AND m.league_id = ${leagueId}
      GROUP BY m.league_id, l.name
      HAVING COUNT(*) >= 20
    `);

    return rows.length > 0 ? rows[0] : null;
  }

  /**
   * Bir upcoming match row'unu MatchCandidate'e dönüştürür
   * ve frekans sinyallerini hesaplar.
   */
  async buildMatchCandidate(
    row: UpcomingMatchRow,
  ): Promise<MatchCandidate | null> {
    const homeOdds = row.ms1_odds ? Number(row.ms1_odds) : 0;
    const awayOdds = row.ms2_odds ? Number(row.ms2_odds) : 0;
    const drawOdds = row.msx_odds ? Number(row.msx_odds) : 0;

    if (homeOdds <= 0 || awayOdds <= 0) {
      return null;
    }

    const signals = await this.getMatchFrequencySignals(
      row.home_team_id,
      row.away_team_id,
      homeOdds,
      awayOdds,
      row.league_id || undefined,
    );

    if (signals.length === 0) {
      return null;
    }

    return {
      matchId: row.match_id,
      homeTeamId: row.home_team_id,
      awayTeamId: row.away_team_id,
      homeTeamName: row.home_team_name,
      awayTeamName: row.away_team_name,
      leagueId: row.league_id,
      leagueName: row.league_name,
      homeOdds,
      awayOdds,
      drawOdds,
      signals,
      bestSignal: signals[0] || null,
      matchTime: Number(row.mst_utc),
    };
  }

  /**
   * Bir market pick'ine karşılık gelen odds'u UpcomingMatchRow'dan çeker.
   */
  getMarketOdds(row: UpcomingMatchRow, market: string): number {
    switch (market) {
      case "OU1.5_OVER":
        return row.ou15_over_odds ? Number(row.ou15_over_odds) : 0;
      case "OU2.5_OVER":
        return row.ou25_over_odds ? Number(row.ou25_over_odds) : 0;
      case "OU2.5_UNDER":
        return row.ou25_under_odds ? Number(row.ou25_under_odds) : 0;
      case "OU3.5_OVER":
        return row.ou35_over_odds ? Number(row.ou35_over_odds) : 0;
      case "BTTS_YES":
        return row.btts_yes_odds ? Number(row.btts_yes_odds) : 0;
      case "MS_HOME":
        return row.ms1_odds ? Number(row.ms1_odds) : 0;
      default:
        return 0;
    }
  }

  // ─────────────────────────────────────────────────────────────
  // Private Helpers
  // ─────────────────────────────────────────────────────────────

  /**
   * Oran bandı fonksiyonu — strategy_generator.py ile aynı mantık.
   */
  getOddsBand(odds: number): string {
    if (odds < 1.3) return "1.00-1.30";
    if (odds < 1.5) return "1.30-1.50";
    if (odds < 1.8) return "1.50-1.80";
    if (odds < 2.2) return "1.80-2.20";
    if (odds < 2.8) return "2.20-2.80";
    if (odds < 4.0) return "2.80-4.00";
    if (odds < 6.0) return "4.00-6.00";
    return "6.00+";
  }

  private parseBandRange(band: string): { min: number; max: number } | null {
    const map: Record<string, { min: number; max: number }> = {
      "1.00-1.30": { min: 1.0, max: 1.3 },
      "1.30-1.50": { min: 1.3, max: 1.5 },
      "1.50-1.80": { min: 1.5, max: 1.8 },
      "1.80-2.20": { min: 1.8, max: 2.2 },
      "2.20-2.80": { min: 2.2, max: 2.8 },
      "2.80-4.00": { min: 2.8, max: 4.0 },
      "4.00-6.00": { min: 4.0, max: 6.0 },
      "6.00+": { min: 6.0, max: 999.0 },
    };
    return map[band] || null;
  }

  private calculateLeagueBonus(profile: LeagueProfileRow | null): number {
    if (!profile || profile.total_matches < 20) {
      return 0;
    }

    // OU2.5 > %60 ise golcü lig bonusu
    if (profile.ou25_rate > 0.6) {
      return Math.min((profile.ou25_rate - 0.5) * 0.2, 0.05);
    }

    // OU2.5 < %40 ise defansif lig bonusu (negatif)
    if (profile.ou25_rate < 0.4) {
      return Math.max((profile.ou25_rate - 0.5) * 0.2, -0.05);
    }

    return 0;
  }

  private calculateConfidence(
    combinedSignal: number,
    homeN: number,
    awayN: number,
    leagueBonus: number,
  ): number {
    // Base confidence: kombine sinyal * 100
    let confidence = combinedSignal * 100;

    // Sample size bonus: daha fazla veri = daha güvenilir
    const minN = Math.min(homeN, awayN);
    if (minN >= 20) {
      confidence += 5;
    } else if (minN >= 10) {
      confidence += 2;
    } else if (minN < 5) {
      confidence -= 5;
    }

    // Liga bonusu
    confidence += leagueBonus * 100;

    return Math.max(0, Math.min(100, parseFloat(confidence.toFixed(1))));
  }
}
