import {
  Injectable,
  Logger,
  NotFoundException,
  ConflictException,
} from "@nestjs/common";
import { PrismaService } from "../../database/prisma.service";
import { TotoFetcherService } from "./services/toto-fetcher.service";
import {
  TotoCombinatoricsService,
  TotoMatchSelectionInput,
} from "./services/toto-combinatorics.service";
import { TotoAnalyticsService } from "./services/toto-analytics.service";
import {
  TotoPredictionService,
  PredictionStrategy,
} from "./services/toto-prediction.service";
import {
  CreateBulletinDto,
  UpdateResultsDto,
  GenerateColumnsDto,
  GenerateSporTotoPredictionDto,
} from "./dto/spor-toto.dto";
import { TotoBulletinStatus, TotoMatchResult, Prisma } from "@prisma/client";

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly fetcher: TotoFetcherService,
    private readonly combinatorics: TotoCombinatoricsService,
    private readonly analytics: TotoAnalyticsService,
    private readonly prediction: TotoPredictionService,
  ) {}

  // ═══════════ BULLETIN CRUD ═══════════

  /**
   * Fetch and sync current bulletin from Spor Toto API
   */
  async syncFromApi(): Promise<{
    action: "created" | "updated" | "unchanged";
    gameCycleNo: number;
    matchCount: number;
  }> {
    const apiResponse = await this.fetcher.fetchCurrentBulletin();
    if (!apiResponse?.data) {
      throw new NotFoundException("Spor Toto API returned no data");
    }

    const apiData = apiResponse.data;
    const existing = await this.prisma.totoBulletin.findUnique({
      where: { gameCycleNo: apiData.gameCycleNo },
      include: { matches: true },
    });

    if (existing) {
      // Update: sonuçları güncelle (eğer varsa)
      let hasChanges = false;

      for (const event of apiData.events) {
        const resultEnum = this.fetcher.mapResultToEnum(event.winner);
        if (resultEnum) {
          const match = existing.matches.find(
            (m) => m.matchOrder === event.eventNo,
          );
          if (match && !match.result) {
            await this.prisma.totoBulletinMatch.update({
              where: { id: match.id },
              data: { result: resultEnum as TotoMatchResult },
            });
            hasChanges = true;
          }
        }
      }

      // Update dividends if present
      if (apiData.dividends) {
        await this.upsertResultsFromDividends(existing.id, apiData.dividends);
        hasChanges = true;
      }

      // Check if all matches have results → mark COMPLETED
      const allHaveResults = apiData.events.every((e) => e.winner !== null);
      if (allHaveResults && existing.status !== "COMPLETED") {
        await this.prisma.totoBulletin.update({
          where: { id: existing.id },
          data: { status: TotoBulletinStatus.COMPLETED },
        });
        hasChanges = true;
      }

      return {
        action: hasChanges ? "updated" : "unchanged",
        gameCycleNo: apiData.gameCycleNo,
        matchCount: apiData.events.length,
      };
    }

    // Create new bulletin
    const matchData = apiData.events.map((event) => {
      const parsed = this.fetcher.parseEventName(event.eventName);
      return {
        matchOrder: event.eventNo,
        homeTeamName: parsed.homeTeam,
        awayTeamName: parsed.awayTeam,
        leagueName: event.competitionName,
        kickoffTime: new Date(event.eventDate),
        result: this.fetcher.mapResultToEnum(
          event.winner,
        ) as TotoMatchResult | null,
      };
    });

    await this.prisma.totoBulletin.create({
      data: {
        gameCycleNo: apiData.gameCycleNo,
        programName: apiData.programName,
        payinBeginDate: new Date(apiData.payinBeginDate),
        payinEndDate: new Date(apiData.payinEndDate),
        status: TotoBulletinStatus.UPCOMING,
        matches: {
          createMany: { data: matchData },
        },
      },
    });

    this.logger.log(
      `Created bulletin: Cycle ${apiData.gameCycleNo} with ${matchData.length} matches`,
    );

    return {
      action: "created",
      gameCycleNo: apiData.gameCycleNo,
      matchCount: matchData.length,
    };
  }

  /**
   * Create bulletin manually
   */
  async createBulletin(dto: CreateBulletinDto) {
    const existing = await this.prisma.totoBulletin.findUnique({
      where: { gameCycleNo: dto.gameCycleNo },
    });

    if (existing) {
      throw new ConflictException(
        `Bulletin with gameCycleNo ${dto.gameCycleNo} already exists`,
      );
    }

    return this.prisma.totoBulletin.create({
      data: {
        gameCycleNo: dto.gameCycleNo,
        programName: dto.programName,
        season: dto.season,
        payinBeginDate: dto.payinBeginDate
          ? new Date(dto.payinBeginDate)
          : undefined,
        payinEndDate: dto.payinEndDate ? new Date(dto.payinEndDate) : undefined,
        matches: {
          createMany: {
            data: dto.matches.map((m) => ({
              matchOrder: m.matchOrder,
              homeTeamName: m.homeTeamName,
              awayTeamName: m.awayTeamName,
              leagueName: m.leagueName,
              kickoffTime: m.kickoffTime ? new Date(m.kickoffTime) : undefined,
              matchId: m.matchId,
            })),
          },
        },
      },
      include: { matches: true },
    });
  }

  /**
   * List bulletins
   */
  async listBulletins(status?: TotoBulletinStatus, limit = 10) {
    const where: Prisma.TotoBulletinWhereInput = {};
    if (status) where.status = status;

    return this.prisma.totoBulletin.findMany({
      where,
      orderBy: { gameCycleNo: "desc" },
      take: limit,
      include: {
        matches: { orderBy: { matchOrder: "asc" } },
        result: true,
      },
    });
  }

  /**
   * Get single bulletin with details
   */
  async getBulletinById(id: string) {
    const bulletin = await this.prisma.totoBulletin.findUnique({
      where: { id },
      include: {
        matches: { orderBy: { matchOrder: "asc" } },
        result: true,
      },
    });

    if (!bulletin) {
      throw new NotFoundException("Bulletin not found");
    }

    return bulletin;
  }

  // ═══════════ RESULTS ═══════════

  /**
   * Update match results manually
   */
  async updateResults(bulletinId: string, dto: UpdateResultsDto) {
    const bulletin = await this.prisma.totoBulletin.findUnique({
      where: { id: bulletinId },
      include: { matches: true },
    });

    if (!bulletin) {
      throw new NotFoundException("Bulletin not found");
    }

    // Update individual match results
    for (const r of dto.results) {
      const match = bulletin.matches.find((m) => m.matchOrder === r.matchOrder);
      if (match) {
        await this.prisma.totoBulletinMatch.update({
          where: { id: match.id },
          data: {
            result: r.result as TotoMatchResult,
            isCancelled: r.isCancelled ?? false,
            drawResult: r.drawResult
              ? (r.drawResult as TotoMatchResult)
              : undefined,
          },
        });
      }
    }

    // Upsert TotoResult record
    if (dto.winners15 !== undefined || dto.winners14 !== undefined) {
      await this.prisma.totoResult.upsert({
        where: { bulletinId },
        create: {
          bulletinId,
          winners15: dto.winners15 ?? 0,
          prize15: dto.prize15,
          winners14: dto.winners14 ?? 0,
          prize14: dto.prize14,
          winners13: dto.winners13 ?? 0,
          prize13: dto.prize13,
          winners12: dto.winners12 ?? 0,
          prize12: dto.prize12,
          rolloverNext: dto.rolloverNext,
        },
        update: {
          winners15: dto.winners15,
          prize15: dto.prize15,
          winners14: dto.winners14,
          prize14: dto.prize14,
          winners13: dto.winners13,
          prize13: dto.prize13,
          winners12: dto.winners12,
          prize12: dto.prize12,
          rolloverNext: dto.rolloverNext,
        },
      });
    }

    // Check if all 15 results entered → mark COMPLETED
    const allEntered = dto.results.length === 15;
    if (allEntered) {
      await this.prisma.totoBulletin.update({
        where: { id: bulletinId },
        data: { status: TotoBulletinStatus.COMPLETED },
      });
    }

    return this.getBulletinById(bulletinId);
  }

  private async upsertResultsFromDividends(
    bulletinId: string,
    dividends: {
      winnerCount15?: number;
      dividend15?: number;
      winnerCount14?: number;
      dividend14?: number;
      winnerCount13?: number;
      dividend13?: number;
      winnerCount12?: number;
      dividend12?: number;
    },
  ) {
    await this.prisma.totoResult.upsert({
      where: { bulletinId },
      create: {
        bulletinId,
        winners15: dividends.winnerCount15 ?? 0,
        prize15: dividends.dividend15 ?? null,
        winners14: dividends.winnerCount14 ?? 0,
        prize14: dividends.dividend14 ?? null,
        winners13: dividends.winnerCount13 ?? 0,
        prize13: dividends.dividend13 ?? null,
        winners12: dividends.winnerCount12 ?? 0,
        prize12: dividends.dividend12 ?? null,
      },
      update: {
        winners15: dividends.winnerCount15 ?? undefined,
        prize15: dividends.dividend15 ?? undefined,
        winners14: dividends.winnerCount14 ?? undefined,
        prize14: dividends.dividend14 ?? undefined,
        winners13: dividends.winnerCount13 ?? undefined,
        prize13: dividends.dividend13 ?? undefined,
        winners12: dividends.winnerCount12 ?? undefined,
        prize12: dividends.dividend12 ?? undefined,
      },
    });
  }

  // ═══════════ COLUMNS & COUPONS ═══════════

  /**
   * Generate columns (system or reduced)
   */
  async generateColumns(dto: GenerateColumnsDto) {
    const bulletin = await this.getBulletinById(dto.bulletinId);

    const matchSelections: TotoMatchSelectionInput[] = dto.matchSelections.map(
      (ms) => ({
        matchOrder: ms.matchOrder,
        selections: ms.selections,
      }),
    );

    const totalColumnCount =
      this.combinatorics.calculateColumnCount(matchSelections);

    let columns;
    const strategy = dto.strategy || "FULL_SYSTEM";

    if (
      strategy === "REDUCED_SYSTEM" &&
      dto.maxColumns &&
      totalColumnCount > dto.maxColumns
    ) {
      columns = this.combinatorics.generateReducedSystem(
        matchSelections,
        dto.maxColumns,
      );
    } else {
      columns = this.combinatorics.generateFullSystem(matchSelections);
    }

    const cost = this.combinatorics.calculateCost(columns.length);

    return {
      bulletinId: bulletin.id,
      gameCycleNo: bulletin.gameCycleNo,
      strategy,
      totalPossibleColumns: totalColumnCount,
      generatedColumns: columns.length,
      cost,
      columns: columns.map((c) => c.predictions),
    };
  }

  /**
   * Evaluate columns against results
   */
  async evaluateColumns(bulletinId: string, columnPredictions: string[]) {
    const bulletin = await this.prisma.totoBulletin.findUnique({
      where: { id: bulletinId },
      include: { matches: { orderBy: { matchOrder: "asc" } } },
    });

    if (!bulletin) {
      throw new NotFoundException("Bulletin not found");
    }

    // Build results string (15 chars)
    const resultMap: Record<string, string> = {
      HOME: "1",
      DRAW: "X",
      AWAY: "2",
    };

    const resultsString = bulletin.matches
      .map((m) => {
        if (m.isCancelled && m.drawResult) {
          return resultMap[m.drawResult] || "?";
        }
        return m.result ? resultMap[m.result] || "?" : "?";
      })
      .join("");

    if (resultsString.includes("?")) {
      return {
        complete: false,
        message: "Bazı maçların sonuçları henüz girilmedi",
        resultsString,
        evaluations: [],
      };
    }

    const columns = columnPredictions.map((p) => ({ predictions: p }));
    const evaluations = this.combinatorics.evaluateColumns(
      columns,
      resultsString,
    );

    const summary = {
      total: evaluations.length,
      correct15: evaluations.filter((e) => e.correctCount === 15).length,
      correct14: evaluations.filter((e) => e.correctCount === 14).length,
      correct13: evaluations.filter((e) => e.correctCount === 13).length,
      correct12: evaluations.filter((e) => e.correctCount === 12).length,
      maxCorrect: Math.max(...evaluations.map((e) => e.correctCount)),
    };

    return {
      complete: true,
      resultsString,
      summary,
      evaluations: evaluations.sort((a, b) => b.correctCount - a.correctCount),
    };
  }

  // ═══════════ ANALYTICS ═══════════

  async getBulletinStats(bulletinId: string) {
    return this.analytics.getBulletinStats(bulletinId);
  }

  async getRolloverHistory(limit = 20) {
    return this.analytics.getRolloverHistory(limit);
  }

  // ═══════════ AI PREDICTION ═══════════

  /**
   * AI Engine ile akıllı sistem kuponu üret
   */
  async generatePrediction(dto: GenerateSporTotoPredictionDto) {
    const strategy: PredictionStrategy = dto.strategy || "BALANCED";
    return this.prediction.generatePrediction(
      dto.bulletinId,
      strategy,
      dto.maxBudget,
    );
  }
}
