import { Prisma, SignalStatus, SourceType, SubscriptionStatus } from "@prisma/client";
import { prisma } from "../../db/prisma.js";
import { createSignalDedupeKey } from "../../lib/dedupe.js";
import { HttpError } from "../../lib/errors.js";
import { settleSignal } from "../../lib/settlement.js";

type UpsertSignalInput = {
  providerId?: string | null;
  eventId: string;
  sportType: string;
  leagueName: string;
  homeTeam: string;
  awayTeam: string;
  eventStartTime: string;
  marketType: string;
  selection: string;
  forecast?: string | null;
  lineValue?: number | null;
  odds: number;
  signalTime: string;
  sourceType: "manual" | "provider";
  comment?: string | null;
  rawPayload?: Record<string, unknown> | null;
  published?: boolean;
};

function normalizeJson(value: Record<string, unknown> | null | undefined): Prisma.InputJsonValue | typeof Prisma.JsonNull | undefined {
  if (value === undefined) return undefined;
  if (value === null) return Prisma.JsonNull;
  return value as Prisma.InputJsonObject;
}

function asWhereArray(value: Prisma.SignalWhereInput["AND"]): Prisma.SignalWhereInput[] {
  if (!value) return [];
  return Array.isArray(value) ? value : [value];
}

const PARSER_INACTIVE_COMMENT = "Signal marked inactive by parser";

function withCreatedAtAsSignalTime<T extends { signalTime: Date; createdAt: Date }>(signal: T) {
  return {
    ...signal,
    signalTime: signal.createdAt
  };
}

function buildActiveSubscriptionWhere(userId: string): Prisma.UserBotAccessWhereInput {
  const now = new Date();

  return {
    userId,
    status: SubscriptionStatus.active,
    startsAt: {
      lte: now
    },
    OR: [
      {
        expiresAt: null
      },
      {
        expiresAt: {
          gte: now
        }
      }
    ]
  };
}

function buildInactiveSignalWhere(): Prisma.SignalWhereInput {
  return {
    comment: PARSER_INACTIVE_COMMENT
  };
}

function buildActiveSignalWhere(): Prisma.SignalWhereInput {
  return {
    OR: [
      {
        comment: null
      },
      {
        comment: {
          not: PARSER_INACTIVE_COMMENT
        }
      }
    ]
  };
}

export async function createSignal(input: UpsertSignalInput) {
  const dedupeKey = createSignalDedupeKey(input);
  try {
    return await prisma.signal.create({
      data: {
        providerId: input.providerId ?? null,
        eventId: input.eventId,
        sportType: input.sportType,
        leagueName: input.leagueName,
        homeTeam: input.homeTeam,
        awayTeam: input.awayTeam,
        eventStartTime: new Date(input.eventStartTime),
        marketType: input.marketType,
        selection: input.selection,
        forecast: input.forecast ?? null,
        lineValue: input.lineValue ?? null,
        odds: input.odds,
        signalTime: new Date(input.signalTime),
        status: SignalStatus.pending,
        sourceType: input.sourceType as SourceType,
        comment: input.comment ?? null,
        published: input.published ?? true,
        dedupeKey,
        rawPayload: normalizeJson(input.rawPayload)
      }
    });
  } catch (error) {
    if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
      throw new HttpError(409, "Такой сигнал уже существует");
    }
    throw error;
  }
}

export async function updateSignal(id: string, input: Partial<UpsertSignalInput> & { status?: SignalStatus }) {
  const current = await prisma.signal.findUnique({ where: { id } });
  if (!current) throw new HttpError(404, "Сигнал не найден");

  const dedupeKey = createSignalDedupeKey({
    providerId: input.providerId ?? current.providerId,
    eventId: input.eventId ?? current.eventId,
    marketType: input.marketType ?? current.marketType,
    selection: input.selection ?? current.selection,
    lineValue: input.lineValue ?? current.lineValue
  });

  return prisma.signal.update({
    where: { id },
    data: {
      providerId: input.providerId ?? undefined,
      eventId: input.eventId,
      sportType: input.sportType,
      leagueName: input.leagueName,
      homeTeam: input.homeTeam,
      awayTeam: input.awayTeam,
      eventStartTime: input.eventStartTime ? new Date(input.eventStartTime) : undefined,
      marketType: input.marketType,
      selection: input.selection,
      forecast: input.forecast === null ? null : input.forecast,
      lineValue: input.lineValue === null ? null : input.lineValue,
      odds: input.odds,
      signalTime: input.signalTime ? new Date(input.signalTime) : undefined,
      sourceType: input.sourceType ? (input.sourceType as SourceType) : undefined,
      comment: input.comment === null ? null : input.comment,
      published: input.published,
      status: input.status,
      dedupeKey,
      rawPayload: normalizeJson(input.rawPayload)
    }
  });
}

export async function listSignals(filters: {
  status?: string;
  sportType?: string;
  sourceType?: string;
  q?: string;
  published?: string;
  botKey?: string;
  activeTab?: number;
  page: number;
  perPage: number;
}, options: { userId?: string; role?: "admin" | "user" } = {}) {
  const publishedFilter = filters.published === undefined ? true : filters.published === "true";
  const page = filters.page;
  const perPage = filters.perPage;

  const where: Prisma.SignalWhereInput = {
    status: filters.status as SignalStatus | undefined,
    sportType: filters.sportType,
    sourceType: filters.sourceType as "manual" | "provider" | undefined,
    published: publishedFilter,
    OR: filters.q
      ? [
          { homeTeam: { contains: filters.q, mode: "insensitive" } },
          { awayTeam: { contains: filters.q, mode: "insensitive" } },
          { leagueName: { contains: filters.q, mode: "insensitive" } }
        ]
      : undefined
  };

  const andConditions: Prisma.SignalWhereInput[] = [];
  let activeTabCondition: Prisma.SignalWhereInput | null = null;

  if (filters.activeTab !== undefined) {
    activeTabCondition = filters.activeTab === 2
      ? buildInactiveSignalWhere()
      : buildActiveSignalWhere();
  }

  if (filters.botKey) {
    andConditions.push({
      OR: [
        {
          rawPayload: {
            path: ["botKey"],
            equals: filters.botKey
          }
        },
        {
          rawPayload: {
            path: ["botKey"],
            equals: String(filters.botKey)
          }
        }
      ]
    });
  }

  if (options.role === "user" && options.userId) {
    const accesses = await prisma.userBotAccess.findMany({
      where: buildActiveSubscriptionWhere(options.userId),
      select: {
        bot: {
          select: {
            key: true
          }
        }
      }
    });

    const allowedBotKeys = accesses.map((entry) => entry.bot.key);

    andConditions.push({
      OR: [
        {
          sourceType: SourceType.manual
        },
        ...allowedBotKeys.flatMap((key) => [
          {
            rawPayload: {
              path: ["botKey"],
              equals: key
            }
          },
          {
            rawPayload: {
              path: ["botKey"],
              equals: String(key)
            }
          }
        ])
      ]
    });
  }

  if (activeTabCondition) {
    andConditions.push(activeTabCondition);
  }

  if (andConditions.length > 0) {
    where.AND = andConditions;
  }

  const tabCountAndConditions = activeTabCondition
    ? andConditions.filter((condition) => condition !== activeTabCondition)
    : andConditions;

  const tabCountsWhere: Prisma.SignalWhereInput | null = filters.botKey || options.role === "user"
    ? {
        status: undefined,
        sportType: filters.sportType,
        sourceType: filters.sourceType as "manual" | "provider" | undefined,
        published: publishedFilter,
        OR: undefined
      }
    : null;

  if (tabCountsWhere && tabCountAndConditions.length > 0) {
    tabCountsWhere.AND = tabCountAndConditions;
  }

  const activeTab1Where: Prisma.SignalWhereInput | null = tabCountsWhere
    ? {
        ...tabCountsWhere,
        AND: [
          ...asWhereArray(tabCountsWhere.AND),
          buildActiveSignalWhere()
        ]
      }
    : null;

  const activeTab2Where: Prisma.SignalWhereInput | null = tabCountsWhere
    ? {
        ...tabCountsWhere,
        AND: [
          ...asWhereArray(tabCountsWhere.AND),
          buildInactiveSignalWhere()
        ]
      }
    : null;

  const [items, total, allTabTotal, activeTabTotal, inactiveTabTotal] = await Promise.all([
    prisma.signal.findMany({
      where,
      orderBy: [{ eventStartTime: "desc" }, { signalTime: "desc" }],
      skip: (page - 1) * perPage,
      take: perPage
    }),
    prisma.signal.count({ where }),
    tabCountsWhere ? prisma.signal.count({ where: tabCountsWhere }) : Promise.resolve(0),
    activeTab1Where ? prisma.signal.count({ where: activeTab1Where }) : Promise.resolve(0),
    activeTab2Where ? prisma.signal.count({ where: activeTab2Where }) : Promise.resolve(0)
  ]);

  return {
    items: items.map(withCreatedAtAsSignalTime),
    pagination: {
      page,
      perPage,
      total,
      totalPages: Math.max(1, Math.ceil(total / perPage))
    },
    tabCounts: tabCountsWhere
      ? {
          all: allTabTotal,
          "1": activeTabTotal,
          "2": inactiveTabTotal
        }
      : undefined
  };
}

export async function listActiveSignalCountsByBot(options: { userId?: string; role?: "admin" | "user" } = {}) {
  const botWhere: Prisma.BotWhereInput = {
    active: true
  };

  if (options.role === "user" && options.userId) {
    botWhere.userAccesses = {
      some: buildActiveSubscriptionWhere(options.userId)
    };
  }

  const bots = await prisma.bot.findMany({
    where: botWhere,
    select: {
      id: true,
      key: true,
      name: true
    },
    orderBy: {
      name: "asc"
    }
  });

  const items = await Promise.all(
    bots.map(async (bot) => {
      const activeSignals = await prisma.signal.count({
        where: {
          published: true,
          OR: [
            {
              rawPayload: {
                path: ["botKey"],
                equals: bot.key
              }
            },
            {
              rawPayload: {
                path: ["botKey"],
                equals: String(bot.key)
              }
            }
          ],
          AND: [
            buildActiveSignalWhere()
          ]
        }
      });

      return {
        botId: bot.id,
        botKey: bot.key,
        botName: bot.name,
        activeSignals
      };
    })
  );

  return {
    items
  };
}

export async function settlePendingSignals() {
  const pendingSignals = await prisma.signal.findMany({
    where: { status: SignalStatus.pending, published: true }
  });

  for (const signal of pendingSignals) {
    const eventResult = await prisma.eventResult.findUnique({ where: { eventId: signal.eventId } });
    if (!eventResult || eventResult.homeScore === null || eventResult.awayScore === null) continue;

    const result = settleSignal(signal, {
      homeScore: eventResult.homeScore,
      awayScore: eventResult.awayScore
    });

    await prisma.signal.update({ where: { id: signal.id }, data: { status: result } });
    await prisma.settlement.upsert({
      where: { signalId: signal.id },
      update: { result, explanation: `Автоматический settlement по счету ${eventResult.homeScore}:${eventResult.awayScore}` },
      create: {
        signalId: signal.id,
        result,
        explanation: `Автоматический settlement по счету ${eventResult.homeScore}:${eventResult.awayScore}`
      }
    });
  }
}
