import { Injectable, Logger } from "@nestjs/common";
import { Cron } from "@nestjs/schedule";
import { ConfigService } from "@nestjs/config";
import { PrismaService } from "../../database/prisma.service";
import axios from "axios";
import * as fs from "fs";
import * as path from "path";

import { ImageRendererService } from "./image-renderer.service";
import {
  competitionLogoUrl,
  countryFlagUrl,
  teamLogoUrl,
} from "../../common/utils/image-url.util";
import { CaptionGeneratorService } from "./caption-generator.service";
import { TwitterService } from "./twitter.service";
import { MetaService } from "./meta.service";
import {
  PredictionCardDto,
  TopPick,
  SocialPostResult,
} from "./dto/prediction-card.dto";

// Top leagues loaded once

const TOP_LEAGUES_PATH = path.join(process.cwd(), "top_leagues.json");
const POSTED_STATE_PATH = path.join(
  process.cwd(),
  "storage",
  "social-poster-posted.json",
);

@Injectable()
export class SocialPosterService {
  private readonly logger = new Logger(SocialPosterService.name);
  private readonly aiEngineUrl: string;
  private readonly appBaseUrl: string;
  private readonly isEnabled: boolean;
  private readonly sports: string[];
  private readonly windowMinMinutes: number;
  private readonly windowMaxMinutes: number;
  private readonly maxPostsPerRun: number;
  private readonly postedMatchIds = new Set<string>();
  private topLeagueIds: Set<string> = new Set();
  private lastRunAt: Date | null = null;
  private lastRunResult: { posted: number; skipped: number; errors: number } = {
    posted: 0,
    skipped: 0,
    errors: 0,
  };

  constructor(
    private readonly prisma: PrismaService,
    private readonly configService: ConfigService,
    private readonly imageRenderer: ImageRendererService,
    private readonly captionGenerator: CaptionGeneratorService,
    private readonly twitterService: TwitterService,
    private readonly metaService: MetaService,
  ) {
    this.aiEngineUrl =
      this.configService.get<string>("AI_ENGINE_URL") ||
      "http://localhost:8000";
    this.appBaseUrl =
      this.configService.get<string>("APP_BASE_URL") || "http://localhost:3000";
    this.isEnabled =
      this.configService.get<string>("SOCIAL_POSTER_ENABLED") === "true";
    this.sports = (
      this.configService.get<string>("SOCIAL_POSTER_SPORTS") ||
      "football,basketball"
    )
      .split(",")
      .map((sport) => sport.trim())
      .filter(Boolean);
    // Default expanded to 10-60 min so each 10-minute cron run has a fresh
    // batch of upcoming matches without overlap (each match falls in one
    // ~10-min slice of the window). Override via env if you want tighter
    // "just before kickoff" timing.
    this.windowMinMinutes = Number(
      this.configService.get<string>("SOCIAL_POSTER_WINDOW_MIN") || 10,
    );
    this.windowMaxMinutes = Number(
      this.configService.get<string>("SOCIAL_POSTER_WINDOW_MAX") || 60,
    );
    this.maxPostsPerRun = Number(
      this.configService.get<string>("SOCIAL_POSTER_MAX_PER_RUN") || 5,
    );

    this.loadTopLeagues();
    this.loadPostedState();
  }

  private loadTopLeagues() {
    try {
      const data = fs.readFileSync(TOP_LEAGUES_PATH, "utf-8");
      const ids = JSON.parse(data);
      this.topLeagueIds = new Set(ids);
      this.logger.log(`✅ Loaded ${this.topLeagueIds.size} top league IDs`);
    } catch {
      this.logger.warn("⚠️ Could not load top_leagues.json");
    }
  }

  private loadPostedState() {
    try {
      const data = fs.readFileSync(POSTED_STATE_PATH, "utf-8");
      const ids = JSON.parse(data);
      if (Array.isArray(ids)) {
        ids.forEach((id) => this.postedMatchIds.add(String(id)));
      }
      this.logger.log(
        `✅ Loaded ${this.postedMatchIds.size} posted social match IDs`,
      );
    } catch {
      this.logger.warn("⚠️ No social poster state file found yet");
    }
  }

  private savePostedState() {
    const dir = path.dirname(POSTED_STATE_PATH);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
    fs.writeFileSync(
      POSTED_STATE_PATH,
      JSON.stringify(Array.from(this.postedMatchIds).slice(-500), null, 2),
    );
  }

  /**
   * Cron: Every 10 minutes, check for upcoming matches and post prediction
   * cards. Window is 10-60 min before kickoff; max posts/run is capped so
   * one cron pass cannot flood Twitter / IG rate limits.
   */
  @Cron("*/10 * * * *")
  async checkAndPostUpcomingMatches() {
    if (!this.isEnabled) return;
    this.lastRunAt = new Date();
    this.lastRunResult = { posted: 0, skipped: 0, errors: 0 };

    try {
      const matches = await this.getUpcomingMatches(
        this.windowMinMinutes,
        this.windowMaxMinutes,
      );
      this.logger.log(
        `📅 Found ${matches.length} upcoming matches in window ` +
          `[${this.windowMinMinutes}-${this.windowMaxMinutes} min]`,
      );

      let postedThisRun = 0;
      for (const match of matches) {
        if (postedThisRun >= this.maxPostsPerRun) {
          this.logger.log(
            `Hit max-per-run cap (${this.maxPostsPerRun}); deferring rest`,
          );
          break;
        }
        if (this.postedMatchIds.has(match.id)) {
          this.lastRunResult.skipped++;
          continue;
        }

        try {
          const result = await this.predictAndPost(match);
          const posted =
            result.twitterPostId ||
            result.facebookPostId ||
            result.instagramPostId;

          if (!posted) {
            this.logger.warn(
              `No platform accepted post for match ${match.id}; will retry later`,
            );
            this.lastRunResult.errors++;
            continue;
          }

          this.postedMatchIds.add(match.id);
          this.lastRunResult.posted++;
          postedThisRun++;

          // Cleanup: remove old IDs (keep last 500)
          if (this.postedMatchIds.size > 500) {
            const arr = Array.from(this.postedMatchIds);
            arr
              .slice(0, arr.length - 500)
              .forEach((id) => this.postedMatchIds.delete(id));
          }
          this.savePostedState();
        } catch (error) {
          this.logger.error(
            `Failed to process match ${match.id}: ${error.message}`,
          );
          this.lastRunResult.errors++;
        }

        // Small delay between posts to avoid rate limits
        await new Promise((resolve) => setTimeout(resolve, 3000));
      }
    } catch (error) {
      this.logger.error(`Cron job failed: ${error.message}`);
      this.lastRunResult.errors++;
    }
  }

  /**
   * Snapshot of the poster's runtime state for the /health endpoint.
   */
  getHealthStatus() {
    return {
      enabled: this.isEnabled,
      sports: this.sports,
      window_min_minutes: this.windowMinMinutes,
      window_max_minutes: this.windowMaxMinutes,
      max_posts_per_run: this.maxPostsPerRun,
      top_leagues_loaded: this.topLeagueIds.size,
      posted_match_count: this.postedMatchIds.size,
      last_run_at: this.lastRunAt?.toISOString() || null,
      last_run_result: this.lastRunResult,
      twitter_available: this.twitterService.available,
      meta_facebook_available: this.metaService.facebookAvailable,
      meta_instagram_available: this.metaService.instagramAvailable,
      ai_engine_url: this.aiEngineUrl,
      app_base_url: this.appBaseUrl,
    };
  }

  /**
   * Get matches starting in [minMinutes, maxMinutes] from now.
   * Filtered by top leagues.
   */
  private async getUpcomingMatches(
    minMinutes: number,
    maxMinutes: number,
  ): Promise<any[]> {
    const now = Date.now();
    const minTime = now + minMinutes * 60 * 1000;
    const maxTime = now + maxMinutes * 60 * 1000;

    const where: any = {
      sport: { in: this.sports },
      mstUtc: {
        gte: minTime,
        lte: maxTime,
      },
    };

    if (this.topLeagueIds.size > 0) {
      where.leagueId = { in: Array.from(this.topLeagueIds) };
    }

    const matches = await this.prisma.liveMatch.findMany({
      where: {
        ...where,
      },
      include: {
        homeTeam: true,
        awayTeam: true,
        league: {
          include: {
            country: true,
          },
        },
      },
      orderBy: {
        mstUtc: "asc",
      },
    });

    return matches;
  }

  /**
   * Full pipeline: Predict → Render Image → Generate Caption → Post.
   */
  async predictAndPost(match: any): Promise<SocialPostResult> {
    const matchId = match.id;
    this.logger.log(
      `🚀 Processing: ${match.homeTeam?.name} vs ${match.awayTeam?.name}`,
    );

    // Step 1: Get prediction from AI Engine
    const prediction = await this.getPrediction(matchId);
    if (!prediction) {
      throw new Error("No prediction returned from AI Engine");
    }

    // Step 2: Build prediction card data
    const card = this.buildCardFromPrediction(match, prediction);

    // Step 3: Render image
    const imagePath = await this.imageRenderer.renderCard(card);
    const imageUrl = `${this.appBaseUrl}${this.imageRenderer.getImageUrl(imagePath)}`;

    // Step 4: Generate caption via Gemini
    const caption = await this.captionGenerator.generateCaption(card);

    // Step 5: Post to all platforms
    const result: SocialPostResult = {
      matchId,
      imagePath,
      caption,
      postedAt: new Date(),
      errors: [],
    };

    // Twitter
    try {
      result.twitterPostId =
        (await this.twitterService.postWithImage(caption, imagePath)) ||
        undefined;
    } catch (error) {
      result.errors!.push(`Twitter: ${error.message}`);
    }

    // Facebook
    try {
      result.facebookPostId =
        (await this.metaService.postToFacebook(caption, imageUrl)) || undefined;
    } catch (error) {
      result.errors!.push(`Facebook: ${error.message}`);
    }

    // Instagram
    try {
      result.instagramPostId =
        (await this.metaService.postToInstagram(caption, imageUrl)) ||
        undefined;
    } catch (error) {
      result.errors!.push(`Instagram: ${error.message}`);
    }

    this.logger.log(
      `✅ Posted: ${match.homeTeam?.name} vs ${match.awayTeam?.name} ` +
        `[TW: ${result.twitterPostId ? "✅" : "❌"}, ` +
        `FB: ${result.facebookPostId ? "✅" : "❌"}, ` +
        `IG: ${result.instagramPostId ? "✅" : "❌"}]`,
    );

    return result;
  }

  /**
   * Call AI Engine's V20+ prediction endpoint directly.
   */
  private async getPrediction(matchId: string): Promise<any> {
    try {
      const response = await axios.post(
        `${this.aiEngineUrl}/v20plus/analyze/${matchId}`,
        null,
        { timeout: 30000 },
      );
      return response.data;
    } catch (error) {
      this.logger.error(`AI Engine request failed: ${error.message}`);
      return null;
    }
  }

  /**
   * Build a PredictionCardDto from the raw AI prediction + match data.
   * Maps the V20+ response structure to our card DTO.
   */
  private buildCardFromPrediction(
    match: any,
    prediction: any,
  ): PredictionCardDto {
    // V20+ returns score_prediction.ft / .ht
    const score = prediction.score_prediction || {};
    const htScore = score.ht || "0-0";
    const ftScore = score.ft || "1-1";

    // Extract best bets from bet_summary array
    const sport = this.normalizeSport(
      match.sport || prediction.match_info?.sport,
    );
    const topPicks = this.extractTopPicks(prediction, sport);

    // Match date formatting
    const matchDate = this.formatMatchDate(match.mstUtc);

    // Score confidence from main_pick or scenario_top5
    const mainPick = prediction.main_pick || {};
    const scoreConfidence = Math.round(
      mainPick.confidence || mainPick.raw_confidence || 50,
    );

    return {
      matchId: match.id,
      sport,
      homeTeam:
        match.homeTeam?.name || prediction.match_info?.home_team || "Home",
      awayTeam:
        match.awayTeam?.name || prediction.match_info?.away_team || "Away",
      homeLogo: teamLogoUrl(match.homeTeamId) ?? "",
      awayLogo: teamLogoUrl(match.awayTeamId) ?? "",
      leagueName: match.league?.name || prediction.match_info?.league || "",
      leagueLogo: competitionLogoUrl(match.leagueId) ?? "",
      countryName:
        match.league?.country?.name ||
        prediction.match_info?.country ||
        this.inferCountryName(match.league?.name || ""),
      countryFlag: countryFlagUrl(match.league?.country?.id) ?? "",
      matchDate,
      htScore,
      ftScore,
      scoreConfidence,
      topPicks,
      riskLevel: prediction.risk?.level || "MEDIUM",
      rawPrediction: prediction,
    };
  }

  /**
   * Extract top 3 picks sorted by confidence from the V20+ bet_summary array.
   */
  private extractTopPicks(
    prediction: any,
    sport: "football" | "basketball",
  ): TopPick[] {
    const betSummary: any[] = prediction.bet_summary || [];

    // Market code to Turkish/English label mapping
    const footballLabels: Record<string, { tr: string; en: string }> = {
      MS: { tr: "Maç Sonucu", en: "Match Result" },
      OU15: { tr: "Üst 1.5 Gol", en: "Over 1.5" },
      OU25: { tr: "Üst 2.5 Gol", en: "Over 2.5" },
      OU35: { tr: "Üst 3.5 Gol", en: "Over 3.5" },
      BTTS: { tr: "Karşılıklı Gol", en: "Both Teams Score" },
      DC: { tr: "Çifte Şans", en: "Double Chance" },
      HT: { tr: "İlk Yarı Sonucu", en: "Half Time Result" },
      HT_OU05: { tr: "İY 0.5 Üst/Alt", en: "HT Over/Under 0.5" },
      OE: { tr: "Tek/Çift", en: "Odd/Even" },
      HTFT: { tr: "İY/MS", en: "HT/FT" },
    };
    const basketballLabels: Record<string, { tr: string; en: string }> = {
      MS: { tr: "Maç Sonucu", en: "Match Result" },
      ML: { tr: "Maç Sonucu", en: "Moneyline" },
      WINNER: { tr: "Maç Sonucu", en: "Winner" },
      TOTAL: { tr: "Toplam Sayı", en: "Total Points" },
      OU: { tr: "Toplam Sayı", en: "Total Points" },
      OVER_UNDER: { tr: "Toplam Sayı", en: "Total Points" },
      HANDICAP: { tr: "Handikap", en: "Handicap" },
      HND: { tr: "Handikap", en: "Handicap" },
      SPREAD: { tr: "Handikap", en: "Spread" },
      HT: { tr: "İlk Devre Sonucu", en: "First Half Result" },
    };
    const marketLabels =
      sport === "basketball" ? basketballLabels : footballLabels;

    const candidates: TopPick[] = betSummary.map((bet) => {
      const labels = marketLabels[bet.market] || {
        tr: bet.market,
        en: bet.market,
      };
      return {
        market: `${labels.tr}: ${bet.pick}`,
        marketEn: `${labels.en}: ${bet.pick}`,
        pick: bet.pick,
        confidence: Math.round(bet.raw_confidence || bet.confidence || 0),
        odds: bet.odds || 0,
      };
    });

    // Sort by confidence and return top 3
    candidates.sort((a, b) => b.confidence - a.confidence);
    return candidates.slice(0, 3);
  }

  private inferCountryName(leagueName: string): string {
    const normalized = leagueName.toLocaleLowerCase("tr-TR");
    if (normalized.includes("süper lig") || normalized.includes("türkiye")) {
      return "Türkiye";
    }
    if (
      normalized.includes("premier league") ||
      normalized.includes("championship")
    ) {
      return "İngiltere";
    }
    if (normalized.includes("la liga")) return "İspanya";
    if (normalized.includes("serie a")) return "İtalya";
    if (normalized.includes("bundesliga")) return "Almanya";
    if (normalized.includes("ligue 1")) return "Fransa";
    if (normalized.includes("euroleague")) return "Avrupa";
    if (normalized.includes("nba")) return "ABD";
    return "";
  }

  private normalizeSport(sport?: string): "football" | "basketball" {
    return String(sport).toLowerCase() === "basketball"
      ? "basketball"
      : "football";
  }

  private formatMatchDate(mstUtc: number | bigint): string {
    const d = new Date(Number(mstUtc));
    const months = [
      "Oca",
      "Şub",
      "Mar",
      "Nis",
      "May",
      "Haz",
      "Tem",
      "Ağu",
      "Eyl",
      "Eki",
      "Kas",
      "Ara",
    ];
    const day = String(d.getDate()).padStart(2, "0");
    const month = months[d.getMonth()];
    const year = d.getFullYear();
    const hour = String(d.getHours()).padStart(2, "0");
    const min = String(d.getMinutes()).padStart(2, "0");
    return `${day} ${month} ${year} - ${hour}:${min}`;
  }

  /**
   * Manual trigger for testing: predict and post for a specific match.
   */
  async manualPost(matchId: string): Promise<SocialPostResult> {
    const match = await this.prisma.liveMatch.findUnique({
      where: { id: matchId },
      include: {
        homeTeam: true,
        awayTeam: true,
        league: {
          include: {
            country: true,
          },
        },
      },
    });

    if (!match) {
      throw new Error(`Match ${matchId} not found`);
    }

    return this.predictAndPost(match);
  }

  /**
   * Manual trigger: render only (no posting) — for preview/testing.
   */
  async renderPreview(
    matchId: string,
  ): Promise<{ imagePath: string; card: PredictionCardDto; caption: string }> {
    const match = await this.prisma.liveMatch.findUnique({
      where: { id: matchId },
      include: {
        homeTeam: true,
        awayTeam: true,
        league: {
          include: {
            country: true,
          },
        },
      },
    });

    if (!match) {
      throw new Error(`Match ${matchId} not found`);
    }

    const prediction = await this.getPrediction(matchId);
    if (!prediction) {
      throw new Error("No prediction returned from AI Engine");
    }

    const card = this.buildCardFromPrediction(match, prediction);
    const imagePath = await this.imageRenderer.renderCard(card);
    const caption = await this.captionGenerator.generateCaption(card);

    return { imagePath, card, caption };
  }
}
