import { z } from "zod";
import { Prisma } from "../../generated/prisma/client";
import { prisma } from "../../lib/prisma";
import { cache } from "../../lib/cache";

export const createWalletSchema = z.object({
  name: z.string().min(1).max(100),
  currency: z.string().min(1).max(10),
  initialBalance: z.number().optional().default(0),
  isDefault: z.boolean().optional().default(false),
});

export const updateWalletSchema = z.object({
  name: z.string().min(1).max(100),
  currency: z.string().min(1).max(10),
});

export const setDefaultWalletSchema = z.object({
  walletId: z.number().int().positive(),
});

export const depositSchema = z.object({
  amount: z.number().positive(),
  description: z.string().max(255).optional(),
});

export const adjustBalanceSchema = z.object({
  newBalance: z.number().min(0),
  description: z.string().max(255).optional(),
});

export const transferSchema = z.object({
  fromWalletId: z.number().int().positive(),
  toWalletId: z.number().int().positive(),
  outgoingAmount: z.number().positive(),
  incomingAmount: z.number().positive(),
  description: z.string().max(255).optional(),
});

export type CreateWalletInput = z.infer<typeof createWalletSchema>;
export type UpdateWalletInput = z.infer<typeof updateWalletSchema>;
export type DepositInput = z.infer<typeof depositSchema>;
export type AdjustBalanceInput = z.infer<typeof adjustBalanceSchema>;
export type TransferInput = z.infer<typeof transferSchema>;
export type SetDefaultWalletInput = z.infer<typeof setDefaultWalletSchema>;

export async function listWallets() {
  const wallets = await prisma.wallet.findMany({
    orderBy: [{ isDefault: "desc" }, { createdAt: "desc" }],
  });
  return wallets.map((w) => ({ ...w, balance: Number(w.balance) }));
}

export async function createWallet(input: CreateWalletInput) {
  const wallet = await prisma.wallet.create({
    data: {
      name: input.name,
      currency: input.currency,
      balance: input.initialBalance ?? 0,
      isDefault: input.isDefault ?? false,
    },
  });
  if (wallet.isDefault) {
    await prisma.wallet.updateMany({
      where: { id: { not: wallet.id }, isDefault: true },
      data: { isDefault: false },
    });
  }
  return { ...wallet, balance: Number(wallet.balance) };
}

export async function updateWallet(id: number, input: UpdateWalletInput) {
  const wallet = await prisma.wallet.update({
    where: { id },
    data: { name: input.name, currency: input.currency },
  });
  return { ...wallet, balance: Number(wallet.balance) };
}

export async function deleteWallet(id: number) {
  const wallet = await prisma.wallet.findUniqueOrThrow({ where: { id } });
  if (Number(wallet.balance) !== 0) {
    throw new Error("No se puede eliminar una billetera con saldo distinto de cero");
  }
  await prisma.walletMovement.deleteMany({ where: { walletId: id } });
  await prisma.wallet.delete({ where: { id } });
}

export async function depositWallet(id: number, input: DepositInput) {
  const [wallet] = await prisma.$transaction([
    prisma.wallet.update({
      where: { id },
      data: { balance: { increment: input.amount } },
    }),
    prisma.walletMovement.create({
      data: {
        walletId: id,
        type: "DEPOSIT",
        amount: input.amount,
        description: input.description ?? null,
      },
    }),
  ]);
  return { ...wallet, balance: Number(wallet.balance) };
}

export async function adjustBalance(id: number, input: AdjustBalanceInput) {
  const wallet = await prisma.wallet.findUniqueOrThrow({ where: { id } });
  const currentBalance = Number(wallet.balance);
  const difference = input.newBalance - currentBalance;

  const [updated] = await prisma.$transaction([
    prisma.wallet.update({
      where: { id },
      data: { balance: input.newBalance },
    }),
    prisma.walletMovement.create({
      data: {
        walletId: id,
        type: "ADJUSTMENT",
        amount: difference,
        description: input.description ?? null,
      },
    }),
  ]);
  return { ...updated, balance: Number(updated.balance) };
}

export async function transferWallet(input: TransferInput) {
  const groupId = crypto.randomUUID();

  const [fromWallet, toWallet] = await prisma.$transaction(async (tx) => {
    const from = await tx.wallet.update({
      where: { id: input.fromWalletId },
      data: { balance: { decrement: input.outgoingAmount } },
    });

    const to = await tx.wallet.update({
      where: { id: input.toWalletId },
      data: { balance: { increment: input.incomingAmount } },
    });

    await tx.walletMovement.create({
      data: {
        walletId: input.fromWalletId,
        type: "TRANSFER_OUT",
        amount: -input.outgoingAmount,
        description: input.description ?? null,
        referenceWalletId: input.toWalletId,
        transferGroupId: groupId,
      },
    });

    await tx.walletMovement.create({
      data: {
        walletId: input.toWalletId,
        type: "TRANSFER_IN",
        amount: input.incomingAmount,
        description: input.description ?? null,
        referenceWalletId: input.fromWalletId,
        transferGroupId: groupId,
      },
    });

    return [from, to];
  });

  return {
    from: { ...fromWallet, balance: Number(fromWallet.balance) },
    to: { ...toWallet, balance: Number(toWallet.balance) },
  };
}

export async function setDefaultWallet(walletId: number) {
  await prisma.$transaction([
    prisma.wallet.updateMany({
      where: { isDefault: true },
      data: { isDefault: false },
    }),
    prisma.wallet.update({
      where: { id: walletId },
      data: { isDefault: true },
    }),
  ]);
  const wallet = await prisma.wallet.findUniqueOrThrow({ where: { id: walletId } });
  return { ...wallet, balance: Number(wallet.balance) };
}

export async function listMovements(walletId: number) {
  const movements = await prisma.walletMovement.findMany({
    where: { walletId },
    orderBy: { createdAt: "desc" },
  });
  return movements.map((m) => ({ ...m, amount: Number(m.amount) }));
}

export async function listAllMovements(params: {
  walletId?: number;
  startDate?: string;
  endDate?: string;
  page?: number;
  pageSize?: number;
}) {
  const { walletId, startDate, endDate, page = 1, pageSize = 20 } = params;

  const cacheKey = `movements:all:${walletId ?? "all"}:${startDate ?? ""}:${endDate ?? ""}:${page}:${pageSize}`;
  const cached = cache.get(cacheKey);
  if (cached) return cached;

  const where: Record<string, unknown> = {};
  if (walletId !== undefined) {
    where.walletId = walletId;
  }
  if (startDate || endDate) {
    const createdAt: Record<string, Date> = {};
    if (startDate) createdAt.gte = new Date(startDate);
    if (endDate) {
      const end = new Date(endDate);
      end.setHours(23, 59, 59, 999);
      createdAt.lte = end;
    }
    where.createdAt = createdAt;
  }

  const [data, total] = await Promise.all([
    prisma.walletMovement.findMany({
      where,
      orderBy: { createdAt: "desc" },
      skip: (page - 1) * pageSize,
      take: pageSize,
    }),
    prisma.walletMovement.count({ where }),
  ]);

  const result = {
    data: data.map((m) => ({ ...m, amount: Number(m.amount) })),
    total,
    page,
    pageSize,
  };
  cache.set(cacheKey, result);
  return result;
}
