import "server-only";
import {
  BetTicketStatus,
  BoostedOddStatus,
  EventStatus,
  LedgerEntryType,
  MarketStatus,
  Prisma,
  SelectionStatus
} from "@prisma/client";
import { z } from "zod";
import { writeAuditLog } from "@/server/audit";
import { prisma } from "@/server/db";
import { AppError } from "@/server/errors";
import { creditPayoutByStakeSourceInTx } from "@/server/vip/rewards";
import { decimal } from "@/server/wallet/ledger";

export const boostedOddInputSchema = z.object({
  id: z.string().uuid().optional(),
  categoryId: z.string().uuid().optional(),
  categoryName: z.string().min(3).max(120).optional(),
  title: z.string().min(3).max(120).optional(),
  sportSlug: z.string().min(2).max(80).default("football"),
  sportName: z.string().min(2).max(80).default("Football"),
  competitionName: z.string().min(2).max(120).default("World Cup Boosts"),
  competitionCountry: z.string().min(2).max(80).default("International"),
  teamA: z.string().min(2).max(120),
  teamB: z.string().min(2).max(120),
  startsAt: z.coerce.date(),
  betLabel: z.string().min(3).max(180),
  originalOdds: z.coerce.number().positive().max(1000),
  boostedOdds: z.coerce.number().positive().max(1000),
  maxStake: z.coerce.number().positive().max(100000),
  allowCombinations: z.coerce.boolean().default(false),
  terms: z.string().min(3).max(300).default("Temps réglementaire uniquement"),
  status: z.nativeEnum(BoostedOddStatus).default(BoostedOddStatus.ACTIVE),
  sortOrder: z.coerce.number().int().min(0).max(10000).default(100)
});

type BoostedOddInput = z.infer<typeof boostedOddInputSchema>;

export const boostedOddCategoryInputSchema = z.object({
  id: z.string().uuid().optional(),
  name: z.string().min(3).max(120),
  status: z.nativeEnum(BoostedOddStatus).default(BoostedOddStatus.ACTIVE),
  sortOrder: z.coerce.number().int().min(0).max(10000).default(100)
});

type BoostedOddCategoryInput = z.infer<typeof boostedOddCategoryInputSchema>;

export const boostedOddSettlementSchema = z.object({
  id: z.string().uuid(),
  outcome: z.enum(["won", "lost", "void"])
});

type BoostedOddSettlementInput = z.infer<typeof boostedOddSettlementSchema>;

type BoostedOddWithRelations = Prisma.BoostedOddGetPayload<{
  include: {
    category: true;
    event: { include: { sport: true; competition: true } };
    market: true;
    selection: true;
  };
}>;

const boostedOddInclude = {
  category: true,
  event: { include: { sport: true, competition: true } },
  market: true,
  selection: true
} satisfies Prisma.BoostedOddInclude;

export async function listAdminBoostedOdds() {
  const boostedOdds = await prisma.boostedOdd.findMany({
    include: boostedOddInclude,
    orderBy: [{ category: { sortOrder: "asc" } }, { sortOrder: "asc" }, { startsAt: "asc" }, { createdAt: "desc" }]
  });

  const openTicketCounts = await prisma.betSelection.groupBy({
    by: ["selectionId"],
    where: {
      selectionId: { in: boostedOdds.map((boostedOdd) => boostedOdd.selectionId) },
      ticket: { status: BetTicketStatus.OPEN }
    },
    _count: { _all: true }
  });
  const openCountBySelectionId = new Map(openTicketCounts.map((item) => [item.selectionId, item._count._all]));

  return boostedOdds.map((boostedOdd) => serializeAdminBoostedOdd(boostedOdd, openCountBySelectionId.get(boostedOdd.selectionId) ?? 0));
}

export async function listBoostedOddCategories() {
  const categories = await prisma.boostedOddCategory.findMany({
    orderBy: [{ sortOrder: "asc" }, { name: "asc" }]
  });

  return categories.map(serializeBoostedOddCategory);
}

export async function upsertBoostedOdd(input: BoostedOddInput) {
  return input.id ? updateBoostedOdd(input.id, input) : createBoostedOdd(input);
}

export async function upsertBoostedOddCategory(input: BoostedOddCategoryInput) {
  const slug = slugify(input.name);
  const category = input.id
    ? await prisma.boostedOddCategory.update({
        where: { id: input.id },
        data: {
          name: input.name,
          slug,
          status: input.status,
          sortOrder: input.sortOrder
        }
      })
    : await prisma.boostedOddCategory.upsert({
        where: { slug },
        create: {
          name: input.name,
          slug,
          status: input.status,
          sortOrder: input.sortOrder
        },
        update: {
          name: input.name,
          status: input.status,
          sortOrder: input.sortOrder
        }
      });

  await prisma.boostedOdd.updateMany({
    where: { categoryId: category.id },
    data: { title: category.name }
  });

  return serializeBoostedOddCategory(category);
}

export async function deleteBoostedOddCategory(id: string) {
  const usage = await prisma.boostedOdd.count({ where: { categoryId: id } });
  if (usage > 0) {
    throw new AppError("Cette catégorie contient encore des cotes boostées.", 422, "BOOSTED_ODD_CATEGORY_IN_USE");
  }

  await prisma.boostedOddCategory.delete({ where: { id } });
  return { id };
}

export async function archiveBoostedOdd(id: string) {
  const current = await prisma.boostedOdd.findUnique({ where: { id } });
  if (!current) {
    throw new AppError("Cote boostée introuvable", 404, "BOOSTED_ODD_NOT_FOUND");
  }

  await prisma.$transaction(async (tx) => {
    await tx.boostedOdd.delete({ where: { id } });
    await tx.selection.update({ where: { id: current.selectionId }, data: { status: SelectionStatus.SUSPENDED } });
    await tx.market.update({ where: { id: current.marketId }, data: { status: MarketStatus.SUSPENDED } });
    await tx.event.update({ where: { id: current.eventId }, data: { status: EventStatus.SUSPENDED } });
  });

  return { id };
}

export async function settleBoostedOdd(input: BoostedOddSettlementInput, actorUserId?: string) {
  const result = await prisma.$transaction(async (tx) => {
    const boostedOdd = await tx.boostedOdd.findUnique({
      where: { id: input.id },
      include: boostedOddInclude
    });

    if (!boostedOdd) {
      throw new AppError("Cote boostée introuvable", 404, "BOOSTED_ODD_NOT_FOUND");
    }

    const tickets = await tx.betTicket.findMany({
      where: {
        status: BetTicketStatus.OPEN,
        selections: { some: { selectionId: boostedOdd.selectionId } }
      },
      include: { selections: true }
    });

    const now = new Date();
    const ticketStatus =
      input.outcome === "won" ? BetTicketStatus.WON : input.outcome === "lost" ? BetTicketStatus.LOST : BetTicketStatus.VOID;
    const selectionStatus =
      input.outcome === "won" ? SelectionStatus.WINNER : input.outcome === "lost" ? SelectionStatus.LOSER : SelectionStatus.VOID;
    const marketStatus = input.outcome === "void" ? MarketStatus.VOIDED : MarketStatus.SETTLED;
    const eventStatus = input.outcome === "void" ? EventStatus.VOIDED : EventStatus.FINISHED;
    const ledgerType = input.outcome === "void" ? LedgerEntryType.BET_VOID : LedgerEntryType.BET_WIN;
    let creditedAmount = decimal(0);

    await tx.selection.update({
      where: { id: boostedOdd.selectionId },
      data: { status: selectionStatus }
    });
    await tx.market.update({
      where: { id: boostedOdd.marketId },
      data: { status: marketStatus }
    });
    await tx.event.update({
      where: { id: boostedOdd.eventId },
      data: { status: eventStatus, isLive: false }
    });
    await tx.boostedOdd.update({
      where: { id: boostedOdd.id },
      data: { status: BoostedOddStatus.EXPIRED }
    });

    for (const ticket of tickets) {
      await tx.betSelection.updateMany({
        where: { ticketId: ticket.id, selectionId: boostedOdd.selectionId },
        data: { status: selectionStatus }
      });

      if (input.outcome !== "lost") {
        const amount = input.outcome === "won" ? ticket.potentialPayout : ticket.stake;
        const stakeSplit = stakeSplitFromMetadata(ticket.metadata);
        creditedAmount = creditedAmount.plus(amount);
        await creditPayoutByStakeSourceInTx(tx, {
          userId: ticket.userId,
          walletId: ticket.walletId,
          stake: ticket.stake,
          payout: amount,
          availableDebit: stakeSplit.available,
          bonusDebit: stakeSplit.bonus,
          type: ledgerType,
          currency: ticket.currency,
          referenceType: "bet_ticket",
          referenceId: ticket.id,
          idempotencyKey: `boosted-odd-settlement:${boostedOdd.id}:${input.outcome}:${ticket.id}`,
          reason: input.outcome === "won" ? "Cote boostée gagnante" : "Cote boostée remboursée",
          metadata: {
            boostedOddId: boostedOdd.id,
            selectionId: boostedOdd.selectionId,
            outcome: input.outcome
          }
        });
      }

      await tx.betTicket.update({
        where: { id: ticket.id },
        data: {
          status: ticketStatus,
          settledAt: now
        }
      });
    }

    await writeAuditLog(
      {
        actorUserId,
        action: "admin.boosted_odd.settle",
        entityType: "BoostedOdd",
        entityId: boostedOdd.id,
        after: {
          outcome: input.outcome,
          ticketsSettled: tickets.length,
          creditedAmount: creditedAmount.toString()
        }
      },
      tx
    );

    return {
      id: boostedOdd.id,
      outcome: input.outcome,
      ticketsSettled: tickets.length,
      creditedAmount: creditedAmount.toString()
    };
  });

  return result;
}

function stakeSplitFromMetadata(metadata: Prisma.JsonValue) {
  if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
    return { available: "0", bonus: "0" };
  }

  const stakeSplit = (metadata as { stakeSplit?: unknown }).stakeSplit;
  if (!stakeSplit || typeof stakeSplit !== "object" || Array.isArray(stakeSplit)) {
    return { available: "0", bonus: "0" };
  }

  const split = stakeSplit as { available?: unknown; bonus?: unknown };
  return {
    available: typeof split.available === "string" || typeof split.available === "number" ? split.available : "0",
    bonus: typeof split.bonus === "string" || typeof split.bonus === "number" ? split.bonus : "0"
  };
}

export function serializeAdminBoostedOdd(boostedOdd: BoostedOddWithRelations, openTickets = 0) {
  return {
    id: boostedOdd.id,
    title: boostedOdd.title,
    category: serializeBoostedOddCategory(boostedOdd.category),
    sportSlug: boostedOdd.event.sport.slug,
    sportName: boostedOdd.event.sport.name,
    competitionName: boostedOdd.event.competition.name,
    competitionCountry: boostedOdd.event.competition.country,
    teamA: boostedOdd.teamA,
    teamB: boostedOdd.teamB,
    startsAt: boostedOdd.startsAt.toISOString(),
    betLabel: boostedOdd.betLabel,
    originalOdds: boostedOdd.originalOdds.toString(),
    boostedOdds: boostedOdd.boostedOdds.toString(),
    maxStake: boostedOdd.maxStake.toString(),
    allowCombinations: boostedOdd.allowCombinations,
    terms: boostedOdd.terms,
    status: boostedOdd.status,
    sortOrder: boostedOdd.sortOrder,
    selectionId: boostedOdd.selectionId,
    oddsVersion: boostedOdd.selection.oddsVersion,
    openTickets
  };
}

function serializeBoostedOddCategory(category: {
  id: string;
  name: string;
  slug: string;
  status: BoostedOddStatus;
  sortOrder: number;
}) {
  return {
    id: category.id,
    name: category.name,
    slug: category.slug,
    status: category.status,
    sortOrder: category.sortOrder
  };
}

async function createBoostedOdd(input: BoostedOddInput) {
  const boostedOdd = await prisma.$transaction(async (tx) => {
    const { sport, competition } = await ensureSportAndCompetition(tx, input);
    const category = await ensureBoostedOddCategory(tx, input);
    const id = crypto.randomUUID();

    const event = await tx.event.create({
      data: {
        providerKey: `boost:${id}`,
        sportId: sport.id,
        competitionId: competition.id,
        name: `${input.teamA} vs ${input.teamB}`,
        homeName: input.teamA,
        awayName: input.teamB,
        startsAt: input.startsAt,
        status: EventStatus.SCHEDULED,
        isLive: false
      }
    });

    const market = await tx.market.create({
      data: {
        eventId: event.id,
        key: "boosted_odd",
        name: "Cote boostée",
        type: "boosted",
        status: MarketStatus.OPEN
      }
    });

    const selection = await tx.selection.create({
      data: {
        marketId: market.id,
        key: "boosted",
        name: input.betLabel,
        odds: new Prisma.Decimal(input.boostedOdds),
        oddsVersion: 1,
        status: SelectionStatus.ACTIVE
      }
    });

    return tx.boostedOdd.create({
      data: {
        id,
        title: category.name,
        categoryId: category.id,
        eventId: event.id,
        marketId: market.id,
        selectionId: selection.id,
        teamA: input.teamA,
        teamB: input.teamB,
        startsAt: input.startsAt,
        betLabel: input.betLabel,
        originalOdds: new Prisma.Decimal(input.originalOdds),
        boostedOdds: new Prisma.Decimal(input.boostedOdds),
        maxStake: new Prisma.Decimal(input.maxStake),
        allowCombinations: input.allowCombinations,
        terms: input.terms,
        status: input.status,
        sortOrder: input.sortOrder
      },
      include: boostedOddInclude
    });
  });

  return serializeAdminBoostedOdd(boostedOdd);
}

async function updateBoostedOdd(id: string, input: BoostedOddInput) {
  const current = await prisma.boostedOdd.findUnique({
    where: { id },
    include: boostedOddInclude
  });

  if (!current) {
    throw new AppError("Cote boostée introuvable", 404, "BOOSTED_ODD_NOT_FOUND");
  }

  const boostedOdd = await prisma.$transaction(async (tx) => {
    const { sport, competition } = await ensureSportAndCompetition(tx, input);
    const category = await ensureBoostedOddCategory(tx, input);
    const nextOdds = new Prisma.Decimal(input.boostedOdds);
    const oddsChanged = !current.selection.odds.equals(nextOdds);

    await tx.event.update({
      where: { id: current.eventId },
      data: {
        sportId: sport.id,
        competitionId: competition.id,
        name: `${input.teamA} vs ${input.teamB}`,
        homeName: input.teamA,
        awayName: input.teamB,
        startsAt: input.startsAt,
        status: input.status === BoostedOddStatus.ACTIVE ? EventStatus.SCHEDULED : EventStatus.SUSPENDED,
        isLive: false
      }
    });

    await tx.market.update({
      where: { id: current.marketId },
      data: {
        name: "Cote boostée",
        type: "boosted",
        status: input.status === BoostedOddStatus.ACTIVE ? MarketStatus.OPEN : MarketStatus.SUSPENDED
      }
    });

    await tx.selection.update({
      where: { id: current.selectionId },
      data: {
        name: input.betLabel,
        odds: nextOdds,
        oddsVersion: oddsChanged ? { increment: 1 } : undefined,
        status: input.status === BoostedOddStatus.ACTIVE ? SelectionStatus.ACTIVE : SelectionStatus.SUSPENDED
      }
    });

    return tx.boostedOdd.update({
      where: { id },
      data: {
        title: category.name,
        categoryId: category.id,
        teamA: input.teamA,
        teamB: input.teamB,
        startsAt: input.startsAt,
        betLabel: input.betLabel,
        originalOdds: new Prisma.Decimal(input.originalOdds),
        boostedOdds: nextOdds,
        maxStake: new Prisma.Decimal(input.maxStake),
        allowCombinations: input.allowCombinations,
        terms: input.terms,
        status: input.status,
        sortOrder: input.sortOrder
      },
      include: boostedOddInclude
    });
  });

  return serializeAdminBoostedOdd(boostedOdd);
}

async function ensureBoostedOddCategory(tx: Prisma.TransactionClient, input: BoostedOddInput) {
  if (input.categoryId) {
    const category = await tx.boostedOddCategory.findUnique({ where: { id: input.categoryId } });
    if (!category) {
      throw new AppError("Catégorie de cote boostée introuvable", 404, "BOOSTED_ODD_CATEGORY_NOT_FOUND");
    }
    return category;
  }

  const name = input.categoryName ?? input.title ?? "Cotes boostées";
  const slug = slugify(name);

  return tx.boostedOddCategory.upsert({
    where: { slug },
    create: {
      name,
      slug,
      status: BoostedOddStatus.ACTIVE,
      sortOrder: 100
    },
    update: {
      name,
      status: BoostedOddStatus.ACTIVE
    }
  });
}

async function ensureSportAndCompetition(tx: Prisma.TransactionClient, input: BoostedOddInput) {
  const sport = await tx.sport.upsert({
    where: { slug: input.sportSlug },
    create: {
      slug: input.sportSlug,
      name: input.sportName,
      icon: input.sportSlug,
      isActive: true,
      sortOrder: input.sportSlug === "football" ? 1 : 100
    },
    update: {
      name: input.sportName,
      isActive: true
    }
  });

  const competition = await tx.competition.upsert({
    where: {
      sportId_name_country: {
        sportId: sport.id,
        name: input.competitionName,
        country: input.competitionCountry
      }
    },
    create: {
      sportId: sport.id,
      providerKey: `boost:${sport.slug}:${slugify(input.competitionCountry)}:${slugify(input.competitionName)}`,
      name: input.competitionName,
      country: input.competitionCountry,
      sortOrder: 1
    },
    update: {
      sortOrder: 1
    }
  });

  return { sport, competition };
}

function slugify(value: string) {
  return value
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "")
    .slice(0, 60);
}
