import { EventStatus, MarketStatus, Prisma, SelectionStatus } from "@prisma/client";
import { prisma } from "@/server/db";
import { env } from "@/server/env";
import { createOddsProvider } from "@/server/providers/odds/factory";
import { mapSelectionStatus, marketMapper, normalizeOdds } from "@/server/providers/odds/normalizer";
import type { ProviderEvent } from "@/server/providers/odds/types";

const EVENT_DETAIL_MARKET_CACHE_MS = 30 * 60 * 1000;

export async function syncOddsFromProvider(input?: { liveOnly?: boolean; sportSlug?: string }) {
  const config = env();
  const provider = createOddsProvider();
  const started = Date.now();
  const startedAt = new Date();
  const windowEnd = new Date(startedAt.getTime() + config.ODDS_PREMATCH_WINDOW_DAYS * 24 * 60 * 60 * 1000);
  let eventCount = 0;
  let marketCount = 0;
  let selectionCount = 0;

  const sports = await provider.listSports();
  const events = (await provider.listEvents(input)).filter((event) => {
    const startsAt = new Date(event.startsAt);
    return !event.isLive && startsAt > startedAt && startsAt <= windowEnd;
  });

  await prisma.$transaction(async (tx) => {
    for (const providerSport of sports) {
      await tx.sport.upsert({
        where: { slug: providerSport.slug },
        create: {
          slug: providerSport.slug,
          name: providerSport.name
        },
        update: {
          name: providerSport.name,
          isActive: true
        }
      });
    }

    for (const providerEvent of events) {
      const sport = await tx.sport.upsert({
        where: { slug: providerEvent.sportSlug },
        create: { slug: providerEvent.sportSlug, name: providerEvent.sportSlug },
        update: {}
      });

      const competition = await tx.competition.upsert({
        where: {
          sportId_name_country: {
            sportId: sport.id,
            name: providerEvent.competition,
            country: providerEvent.country
          }
        },
        create: {
          sportId: sport.id,
          providerKey: `${providerEvent.sportSlug}:${providerEvent.country}:${providerEvent.competition}`,
          name: providerEvent.competition,
          country: providerEvent.country
        },
        update: {}
      });

      const event = await tx.event.upsert({
        where: { providerKey: providerEvent.externalId },
        create: {
          providerKey: providerEvent.externalId,
          sportId: sport.id,
          competitionId: competition.id,
          name: `${providerEvent.homeName} vs ${providerEvent.awayName}`,
          homeName: providerEvent.homeName,
          awayName: providerEvent.awayName,
          startsAt: new Date(providerEvent.startsAt),
          status: EventStatus.SCHEDULED,
          isLive: false,
          minute: providerEvent.minute,
          scoreHome: providerEvent.scoreHome,
          scoreAway: providerEvent.scoreAway
        },
        update: {
          competitionId: competition.id,
          startsAt: new Date(providerEvent.startsAt),
          status: EventStatus.SCHEDULED,
          isLive: false,
          minute: providerEvent.minute,
          scoreHome: providerEvent.scoreHome,
          scoreAway: providerEvent.scoreAway
        }
      });

      eventCount += 1;

      const providerMarketKeys = providerEvent.markets.map((market) => market.key);

      for (const providerMarket of providerEvent.markets) {
        const mapped = marketMapper(providerMarket.key);
        const providerSelectionKeys = providerMarket.selections.map((selection) => selection.key);
        const market = await tx.market.upsert({
          where: {
            eventId_key: {
              eventId: event.id,
              key: providerMarket.key
            }
          },
          create: {
            eventId: event.id,
            key: providerMarket.key,
            name: providerMarket.name || mapped.name,
            type: providerMarket.type || mapped.type,
            status: MarketStatus.OPEN
          },
          update: {
            name: providerMarket.name || mapped.name,
            type: providerMarket.type || mapped.type,
            status: MarketStatus.OPEN
          }
        });
        marketCount += 1;

        for (const providerSelection of providerMarket.selections) {
          const odds = normalizeOdds(providerSelection.odds);
          const existing = await tx.selection.findUnique({
            where: {
              marketId_key: {
                marketId: market.id,
                key: providerSelection.key
              }
            }
          });

          if (!existing) {
            await tx.selection.create({
              data: {
                marketId: market.id,
                key: providerSelection.key,
                name: providerSelection.name,
                odds,
                status: mapSelectionStatus(providerSelection.status)
              }
            });
          } else {
            const oddsChanged = !existing.odds.equals(odds);
            await tx.selection.update({
              where: { id: existing.id },
              data: {
                name: providerSelection.name,
                odds,
                status: mapSelectionStatus(providerSelection.status),
                oddsVersion: oddsChanged ? { increment: 1 } : undefined
              }
            });
          }

          selectionCount += 1;
        }

        await tx.selection.updateMany({
          where: {
            marketId: market.id,
            ...(providerSelectionKeys.length > 0 ? { key: { notIn: providerSelectionKeys } } : {})
          },
          data: { status: SelectionStatus.SUSPENDED }
        });
      }

      await tx.market.updateMany({
        where: {
          eventId: event.id,
          ...(providerMarketKeys.length > 0 ? { key: { notIn: providerMarketKeys } } : {})
        },
        data: { status: MarketStatus.SUSPENDED }
      });
    }

    if (config.ODDS_PROVIDER !== "local") {
      await tx.event.updateMany({
        where: {
          providerKey: { startsWith: "local-" },
          status: { in: [EventStatus.SCHEDULED, EventStatus.LIVE] }
        },
        data: {
          status: EventStatus.SUSPENDED,
          isLive: false
        }
      });
    }

    await tx.selection.updateMany({
      where: {
        market: {
          event: {
            status: { in: [EventStatus.SCHEDULED, EventStatus.LIVE] },
            startsAt: { lte: startedAt }
          }
        }
      },
      data: { status: SelectionStatus.SUSPENDED }
    });

    await tx.market.updateMany({
      where: {
        event: {
          status: { in: [EventStatus.SCHEDULED, EventStatus.LIVE] },
          startsAt: { lte: startedAt }
        }
      },
      data: { status: MarketStatus.SUSPENDED }
    });

    await tx.event.updateMany({
      where: {
        status: { in: [EventStatus.SCHEDULED, EventStatus.LIVE] },
        startsAt: { lte: startedAt }
      },
      data: {
        status: EventStatus.FINISHED,
        isLive: false,
        minute: null
      }
    });

    await tx.providerLog.create({
      data: {
        provider: "odds",
        direction: "inbound",
        operation: "sync",
        status: "success",
        latencyMs: Date.now() - started,
        payload: { eventCount, marketCount, selectionCount }
      }
    });
  });

  return { eventCount, marketCount, selectionCount, latencyMs: Date.now() - started };
}

export async function refreshEventMarketsFromProvider(eventId: string) {
  const config = env();
  if (config.ODDS_PROVIDER === "local") return { refreshed: false, reason: "local-provider" };

  const event = await prisma.event.findUnique({
    where: { id: eventId },
    include: {
      sport: true,
      markets: {
        select: { updatedAt: true }
      }
    }
  });

  if (!event?.providerKey?.startsWith("the-odds-api:")) {
    return { refreshed: false, reason: "unsupported-provider" };
  }

  const now = new Date();
  const windowEnd = new Date(now.getTime() + config.ODDS_PREMATCH_WINDOW_DAYS * 24 * 60 * 60 * 1000);
  if (event.status !== EventStatus.SCHEDULED || event.isLive || event.startsAt <= now || event.startsAt > windowEnd) {
    return { refreshed: false, reason: "event-not-open" };
  }

  const latestMarketUpdate = event.markets.reduce<Date | null>(
    (latest, market) => (!latest || market.updatedAt > latest ? market.updatedAt : latest),
    null
  );
  if (event.markets.length > 3 && latestMarketUpdate && latestMarketUpdate.getTime() > now.getTime() - EVENT_DETAIL_MARKET_CACHE_MS) {
    return { refreshed: false, reason: "fresh" };
  }

  const provider = createOddsProvider();
  if (!provider.getEventByExternalId) return { refreshed: false, reason: "provider-not-supported" };

  const providerEvent = await provider.getEventByExternalId(event.providerKey, { sportSlug: event.sport.slug });
  if (!providerEvent) return { refreshed: false, reason: "not-found" };

  const counts = await prisma.$transaction(async (tx) => {
    await tx.event.update({
      where: { id: event.id },
      data: {
        startsAt: new Date(providerEvent.startsAt),
        status: EventStatus.SCHEDULED,
        isLive: false,
        minute: null,
        scoreHome: null,
        scoreAway: null
      }
    });

    return upsertProviderMarkets(tx, event.id, providerEvent);
  });

  return { refreshed: true, ...counts };
}

async function upsertProviderMarkets(tx: Prisma.TransactionClient, eventId: string, providerEvent: ProviderEvent) {
  let marketCount = 0;
  let selectionCount = 0;

  for (const providerMarket of providerEvent.markets) {
    const mapped = marketMapper(providerMarket.key);
    const market = await tx.market.upsert({
      where: {
        eventId_key: {
          eventId,
          key: providerMarket.key
        }
      },
      create: {
        eventId,
        key: providerMarket.key,
        name: providerMarket.name || mapped.name,
        type: providerMarket.type || mapped.type,
        status: MarketStatus.OPEN
      },
      update: {
        name: providerMarket.name || mapped.name,
        type: providerMarket.type || mapped.type,
        status: MarketStatus.OPEN
      }
    });
    marketCount += 1;

    for (const providerSelection of providerMarket.selections) {
      const odds = normalizeOdds(providerSelection.odds);
      const existing = await tx.selection.findUnique({
        where: {
          marketId_key: {
            marketId: market.id,
            key: providerSelection.key
          }
        }
      });

      if (!existing) {
        await tx.selection.create({
          data: {
            marketId: market.id,
            key: providerSelection.key,
            name: providerSelection.name,
            odds,
            status: mapSelectionStatus(providerSelection.status)
          }
        });
      } else {
        const oddsChanged = !existing.odds.equals(odds);
        await tx.selection.update({
          where: { id: existing.id },
          data: {
            name: providerSelection.name,
            odds,
            status: mapSelectionStatus(providerSelection.status),
            oddsVersion: oddsChanged ? { increment: 1 } : undefined
          }
        });
      }

      selectionCount += 1;
    }
  }

  return { marketCount, selectionCount };
}
