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 { 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");

@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 postedMatchIds = new Set<string>();
  private topLeagueIds: Set<string> = new Set();

  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.loadTopLeagues();
  }

  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");
    }
  }

  /**
   * Cron: Every 10 minutes, check for upcoming matches.
   * Posts predictions 30 minutes before kickoff.
   */
  @Cron("*/10 * * * *")
  async checkAndPostUpcomingMatches() {
    if (!this.isEnabled) return;

    try {
      const matches = await this.getUpcomingMatches(25, 40); // 25-40 min window
      this.logger.log(
        `📅 Found ${matches.length} upcoming matches in the window`,
      );

      for (const match of matches) {
        if (this.postedMatchIds.has(match.id)) continue;

        try {
          await this.predictAndPost(match);
          this.postedMatchIds.add(match.id);

          // 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));
          }
        } catch (error) {
          this.logger.error(
            `Failed to process match ${match.id}: ${error.message}`,
          );
        }

        // 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}`);
    }
  }

  /**
   * 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 matches = await this.prisma.liveMatch.findMany({
      where: {
        sport: "football",
        leagueId: { in: Array.from(this.topLeagueIds) },
        mstUtc: {
          gte: minTime,
          lte: maxTime,
        },
      },
      include: {
        homeTeam: true,
        awayTeam: true,
        league: true,
      },
    });

    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 topPicks = this.extractTopPicks(prediction);

    // 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,
      homeTeam:
        match.homeTeam?.name || prediction.match_info?.home_team || "Home",
      awayTeam:
        match.awayTeam?.name || prediction.match_info?.away_team || "Away",
      homeLogo: this.resolveLogoUrl(match.homeTeam?.logoUrl || ""),
      awayLogo: this.resolveLogoUrl(match.awayTeam?.logoUrl || ""),
      leagueName: match.league?.name || prediction.match_info?.league || "",
      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): TopPick[] {
    const betSummary: any[] = prediction.bet_summary || [];

    // Market code to Turkish/English label mapping
    const marketLabels: 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 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);
  }

  /**
   * Convert relative logo paths to full HTTP URLs.
   * On the deployed server, logos exist at public/uploads/teams/...
   * Locally during dev, we fetch them from the deployed server via APP_BASE_URL.
   */
  private resolveLogoUrl(logoUrl: string): string {
    if (!logoUrl) return "";
    // Already a full URL
    if (logoUrl.startsWith("http")) return logoUrl;
    // Relative path → check local first, otherwise make full URL
    const localPath = path.join(process.cwd(), "public", logoUrl);
    if (fs.existsSync(localPath)) return logoUrl; // Keep relative, renderer reads local
    // Not local → prepend base URL for remote fetch
    return `${this.appBaseUrl}${logoUrl}`;
  }

  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: 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: 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 };
  }
}
