import { Router } from "express";
import { Request, Response } from "express";
import { storage } from "../storage";
import { z } from "zod";
import { ZodError } from "zod";
import {
  insertPaymentSchema,
  NewTransactionPayload,
  newTransactionPayloadSchema,
  paymentMethodOptions,
  PaymentStatus,
  paymentStatusOptions,
  claimStatusOptions,
} from "@repo/db/types";
import { prisma } from "@repo/db/client";
import { PaymentStatusSchema } from "@repo/db/types";
import * as paymentService from "../services/paymentService";
import { callPythonSync } from "../queue/processors/_shared";
import { runMhBatchPaymentCheck } from "../services/mhBatchPaymentService";

const paymentFilterSchema = z.object({
  from: z.string().datetime(),
  to: z.string().datetime(),
});

function parseIntOrError(input: string | undefined, name: string) {
  if (!input) throw new Error(`${name} is required`);
  const value = parseInt(input, 10);
  if (isNaN(value)) throw new Error(`${name} must be a valid number`);
  return value;
}

export function handleRouteError(
  res: Response,
  error: unknown,
  defaultMsg: string
) {
  if (error instanceof ZodError) {
    return res.status(400).json({
      message: "Validation error",
      errors: error.format(),
    });
  }

  const msg = error instanceof Error ? error.message : defaultMsg;
  return res.status(500).json({ message: msg });
}

const router = Router();

// GET /api/payments/recent
router.get("/recent", async (req: Request, res: Response): Promise<any> => {
  try {
    const limit = parseInt(req.query.limit as string) || 10;
    const offset = parseInt(req.query.offset as string) || 0;

    const [payments, totalCount] = await Promise.all([
      storage.getRecentPayments(limit, offset),
      storage.getTotalPaymentCount(),
    ]);

    res.status(200).json({ payments, totalCount });
  } catch (err) {
    console.error("Failed to fetch payments:", err);
    res.status(500).json({ message: "Failed to fetch recent payments" });
  }
});

// GET /api/payments/claim/:claimId
router.get(
  "/claim/:claimId",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const parsedClaimId = parseIntOrError(req.params.claimId, "Claim ID");

      const payments = await storage.getPaymentsByClaimId(parsedClaimId);
      if (!payments)
        return res.status(404).json({ message: "No payments found for claim" });

      res.status(200).json(payments);
    } catch (error) {
      console.error("Error fetching payments:", error);
      res.status(500).json({ message: "Failed to retrieve payments" });
    }
  }
);

// GET /api/payments/patient/:patientId
router.get(
  "/patient/:patientId",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const patientIdParam = req.params.patientId;
      if (!patientIdParam) {
        return res.status(400).json({ message: "Missing patientId" });
      }
      const patientId = parseInt(patientIdParam);
      if (isNaN(patientId)) {
        return res.status(400).json({ message: "Invalid patientId" });
      }
      const limit = parseInt(req.query.limit as string) || 10;
      const offset = parseInt(req.query.offset as string) || 0;

      if (isNaN(patientId)) {
        return res.status(400).json({ message: "Invalid patient ID" });
      }

      const [payments, totalCount] = await Promise.all([
        storage.getRecentPaymentsByPatientId(patientId, limit, offset),
        storage.getTotalPaymentCountByPatient(patientId),
      ]);

      res.json({ payments, totalCount });
    } catch (error) {
      console.error("Failed to retrieve payments for patient:", error);
      res.status(500).json({ message: "Failed to retrieve patient payments" });
    }
  }
);

// GET /api/payments/filter
router.get("/filter", async (req: Request, res: Response): Promise<any> => {
  try {
    const validated = paymentFilterSchema.safeParse(req.query);
    if (!validated.success) {
      return res.status(400).json({
        message: "Invalid date format",
        errors: validated.error.errors,
      });
    }

    const { from, to } = validated.data;
    const payments = await storage.getPaymentsByDateRange(
      new Date(from),
      new Date(to)
    );
    res.status(200).json(payments);
  } catch (err) {
    console.error("Failed to filter payments:", err);
    res.status(500).json({ message: "Server error" });
  }
});

// GET /api/payments/:id
router.get("/:id", async (req: Request, res: Response): Promise<any> => {
  try {
    const id = parseIntOrError(req.params.id, "Payment ID");

    const payment = await storage.getPaymentById(id);
    if (!payment) return res.status(404).json({ message: "Payment not found" });

    res.status(200).json(payment);
  } catch (err: unknown) {
    const message =
      err instanceof Error ? err.message : "Failed to retrieve payment";
    res.status(500).json({ message });
  }
});

// POST /api/payments/full-ocr-import
router.post(
  "/full-ocr-import",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) return res.status(401).json({ message: "Unauthorized" });

      const { rows } = req.body;
      if (!rows || !Array.isArray(rows)) {
        return res.status(400).json({ message: "Invalid OCR payload" });
      }

      const paymentIds = await paymentService.fullOcrPaymentService.importRows(
        rows,
        userId
      );

      res.status(200).json({
        message: "OCR rows imported successfully",
        paymentIds,
      });
    } catch (err) {
      console.error(err);

      if (err instanceof Error) {
        return res.status(500).json({ message: err.message });
      }

      return res
        .status(500)
        .json({ message: "Unknown error importing OCR payments" });
    }
  }
);

// POST /api/payments/mh-batch-payment-check
router.post(
  "/mh-batch-payment-check",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) return res.status(401).json({ message: "Unauthorized" });

      const { fromDate, toDate } = req.body;
      const result = await runMhBatchPaymentCheck(userId, fromDate, toDate);
      return res.json(result);
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : "MH batch payment check failed";
      return res.status(500).json({ message });
    }
  }
);

// POST /api/payments/:claimId
router.post("/:claimId", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const claimId = parseIntOrError(req.params.claimId, "Claim ID");

    // Inherit npiProviderId from the linked claim so commission queries work
    const linkedClaim = await prisma.claim.findUnique({
      where: { id: claimId },
      select: { npiProviderId: true },
    });

    const validated = insertPaymentSchema.safeParse({
      ...req.body,
      claimId,
      userId,
      ...(linkedClaim?.npiProviderId ? { npiProviderId: linkedClaim.npiProviderId } : {}),
    });

    if (!validated.success) {
      return res.status(400).json({
        message: "Validation failed",
        errors: validated.error.flatten(),
      });
    }

    const payment = await storage.createPayment(validated.data);
    res.status(201).json(payment);
  } catch (err: unknown) {
    const message =
      err instanceof Error ? err.message : "Failed to create payment";
    res.status(500).json({ message });
  }
});

// PUT /api/payments/:id
router.put("/:id", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const paymentId = parseIntOrError(req.params.id, "Payment ID");

    const validated = newTransactionPayloadSchema.safeParse(
      req.body.data as NewTransactionPayload
    );
    if (!validated.success) {
      return res.status(400).json({
        message: "Validation failed",
        errors: validated.error.flatten(),
      });
    }

    const { serviceLineTransactions } = validated.data;

    const updatedPayment = await paymentService.updatePayment(
      paymentId,
      serviceLineTransactions,
      userId
    );

    res.status(200).json(updatedPayment);
  } catch (err: unknown) {
    const message =
      err instanceof Error ? err.message : "Failed to update payment";
    res.status(500).json({ message });
  }
});

// PUT /api/payments/:id/pay-absolute-full-claim
router.put(
  "/:id/pay-absolute-full-claim",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) return res.status(401).json({ message: "Unauthorized" });

      const paymentId = parseIntOrError(req.params.id, "Payment ID");
      const paymentRecord = await storage.getPaymentById(paymentId);
      if (!paymentRecord) {
        return res.status(404).json({ message: "Payment not found" });
      }

      // Collect service lines from either claim or direct payment(OCR based data)
      const serviceLines = paymentRecord.claim
        ? paymentRecord.claim.serviceLines
        : paymentRecord.serviceLines;

      if (!serviceLines || serviceLines.length === 0) {
        return res
          .status(400)
          .json({ message: "No service lines available for this payment" });
      }

      const serviceLineTransactions = serviceLines
        .filter((line) => line.totalDue.gt(0))
        .map((line) => ({
          serviceLineId: line.id,
          paidAmount: line.totalDue.toNumber(),
          adjustedAmount: 0,
          method: paymentMethodOptions.CHECK,
          receivedDate: new Date(),
          notes: "Full claim payment",
        }));

      if (serviceLineTransactions.length === 0) {
        return res.status(400).json({ message: "No outstanding balance" });
      }

      // Use updatePayment for consistency & validation
      const updatedPayment = await paymentService.updatePayment(
        paymentId,
        serviceLineTransactions,
        userId
      );

      res.status(200).json(updatedPayment);
    } catch (err) {
      console.error(err);
      res.status(500).json({ message: "Failed to pay full claim" });
    }
  }
);

// PUT /api/payments/:id/revert-full-claim
router.put(
  "/:id/revert-full-claim",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) return res.status(401).json({ message: "Unauthorized" });

      const paymentId = parseIntOrError(req.params.id, "Payment ID");
      const paymentRecord = await storage.getPaymentById(paymentId);
      if (!paymentRecord) {
        return res.status(404).json({ message: "Payment not found" });
      }

      const serviceLines = paymentRecord.claim
        ? paymentRecord.claim.serviceLines
        : paymentRecord.serviceLines;

      if (!serviceLines || serviceLines.length === 0) {
        return res
          .status(400)
          .json({ message: "No service lines available for this payment" });
      }

      // Build reversal transactions (negating what’s already paid/adjusted)
      const serviceLineTransactions = serviceLines
        .filter((line) => line.totalPaid.gt(0) || line.totalAdjusted.gt(0))
        .map((line) => ({
          serviceLineId: line.id,
          paidAmount: line.totalPaid.negated().toNumber(), // negative to undo
          adjustedAmount: line.totalAdjusted.negated().toNumber(),
          method: paymentMethodOptions.OTHER,
          receivedDate: new Date(),
          notes: "Reverted full claim",
        }));

      if (serviceLineTransactions.length === 0) {
        return res.status(400).json({ message: "Nothing to revert" });
      }

      const updatedPayment = await paymentService.updatePayment(
        paymentId,
        serviceLineTransactions,
        userId,
        { isReversal: true }
      );

      res.status(200).json(updatedPayment);
    } catch (err) {
      console.error(err);
      res.status(500).json({ message: "Failed to revert claim payments" });
    }
  }
);

// PATCH /api/payments/:id/status
router.patch(
  "/:id/status",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) {
        return res.status(401).json({ message: "Unauthorized" });
      }

      const paymentId = parseIntOrError(req.params.id, "Payment ID");

      // Parse & coerce to PaymentStatus enum
      const rawStatus = PaymentStatusSchema.parse(req.body.data.status);
      if (
        !Object.values(paymentStatusOptions).includes(
          rawStatus as PaymentStatus
        )
      ) {
        return res.status(400).json({ message: "Invalid payment status" });
      }
      const status = rawStatus as PaymentStatus;

      // Load existing payment
      const existingPayment = await storage.getPayment(paymentId);
      if (!existingPayment) {
        return res.status(404).json({ message: "Payment not found" });
      }

      // If changing to VOID and linked to a claim -> update both atomically
      if (status === paymentStatusOptions.VOID && existingPayment.claimId) {
        const [updatedPayment, updatedClaim] = await prisma.$transaction([
          prisma.payment.update({
            where: { id: paymentId },
            data: { status, updatedById: userId },
          }),
          prisma.claim.update({
            where: { id: existingPayment.claimId },
            data: { status: claimStatusOptions.VOID },
          }),
        ]);

        return res.json(updatedPayment);
      }

      // Otherwise just update payment (use storage helper)
      const updatedPayment = await storage.updatePaymentStatus(
        paymentId,
        { status } as any,
        userId
      );

      return res.json(updatedPayment);
    } catch (err: unknown) {
      const message =
        err instanceof Error ? err.message : "Failed to update payment status";
      return res.status(500).json({ message });
    }
  }
);

// PATCH /api/payments/:id/provider
router.patch(
  "/:id/provider",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) return res.status(401).json({ message: "Unauthorized" });

      const paymentId = parseIntOrError(req.params.id, "Payment ID");
      const { npiProviderId } = req.body;

      const existing = await storage.getPayment(paymentId);
      if (!existing) return res.status(404).json({ message: "Payment not found" });

      // Update payment and linked claim atomically so the claims form
      // picks up the new provider when it prefills from the existing claim.
      const ops: Parameters<typeof prisma.$transaction>[0] = [
        prisma.payment.update({
          where: { id: paymentId },
          data: { npiProviderId: npiProviderId ?? null, updatedById: userId },
          include: { npiProvider: true },
        }),
      ];

      if (existing.claimId) {
        ops.push(
          prisma.claim.update({
            where: { id: existing.claimId },
            data: { npiProviderId: npiProviderId ?? null },
          }) as any
        );
      }

      const [updated] = await prisma.$transaction(ops);
      return res.json(updated);
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : "Failed to update provider";
      return res.status(500).json({ message });
    }
  }
);

// PATCH /api/payments/:id/mh-paid-amount
router.patch(
  "/:id/mh-paid-amount",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) return res.status(401).json({ message: "Unauthorized" });

      const paymentId = parseIntOrError(req.params.id, "Payment ID");
      const raw = req.body.mhPaidAmount;
      const mhPaidAmount = parseFloat(raw);
      if (isNaN(mhPaidAmount) || mhPaidAmount < 0) {
        return res.status(400).json({ message: "Invalid mhPaidAmount value" });
      }

      const existing = await prisma.payment.findUnique({ where: { id: paymentId } });
      if (!existing) return res.status(404).json({ message: "Payment not found" });

      const totalBilled  = Number(existing.totalBilled);
      const copayment    = Number(existing.copayment ?? 0);
      const adjustment   = Math.max(0, totalBilled - mhPaidAmount - copayment);

      const updated = await prisma.payment.update({
        where: { id: paymentId },
        data: { mhPaidAmount, adjustment, updatedById: userId },
      });

      return res.json({ ...updated, mhPaidAmount: Number(updated.mhPaidAmount), adjustment: Number(updated.adjustment) });
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : "Failed to update MH paid amount";
      return res.status(500).json({ message });
    }
  }
);

// PATCH /api/payments/:id/copayment
router.patch(
  "/:id/copayment",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) return res.status(401).json({ message: "Unauthorized" });

      const paymentId = parseIntOrError(req.params.id, "Payment ID");
      const val = parseFloat(req.body.copayment);
      if (isNaN(val) || val < 0) {
        return res.status(400).json({ message: "Invalid copayment value" });
      }

      const existing = await prisma.payment.findUnique({ where: { id: paymentId } });
      if (!existing) return res.status(404).json({ message: "Payment not found" });

      const totalBilled  = Number(existing.totalBilled);
      const mhPaidAmount = Number(existing.mhPaidAmount ?? 0);
      const adjustment   = Math.max(0, totalBilled - mhPaidAmount - val);

      const updated = await prisma.payment.update({
        where: { id: paymentId },
        data: { copayment: val, adjustment, updatedById: userId },
      });

      return res.json({ ...updated, copayment: Number(updated.copayment), adjustment: Number(updated.adjustment) });
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : "Failed to update copayment";
      return res.status(500).json({ message });
    }
  }
);

// PATCH /api/payments/:id/adjustment
router.patch(
  "/:id/adjustment",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) return res.status(401).json({ message: "Unauthorized" });

      const paymentId = parseIntOrError(req.params.id, "Payment ID");
      const val = parseFloat(req.body.adjustment);
      if (isNaN(val) || val < 0) {
        return res.status(400).json({ message: "Invalid adjustment value" });
      }

      const updated = await prisma.payment.update({
        where: { id: paymentId },
        data: { adjustment: val, updatedById: userId },
      });

      return res.json({ ...updated, adjustment: Number(updated.adjustment) });
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : "Failed to update adjustment";
      return res.status(500).json({ message });
    }
  }
);

// PATCH /api/payments/:id/mh-payment-check
router.patch(
  "/:id/mh-payment-check",
  async (req: Request, res: Response): Promise<any> => {
    try {
      const userId = req.user?.id;
      if (!userId) return res.status(401).json({ message: "Unauthorized" });

      const paymentId = parseIntOrError(req.params.id, "Payment ID");

      const payment = await storage.getPaymentById(paymentId);
      if (!payment) return res.status(404).json({ message: "Payment not found" });

      const claimNumber = payment.claim?.claimNumber;
      if (!claimNumber) {
        return res.status(400).json({ message: "No claim number found for this payment" });
      }

      const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(userId, "MH");
      if (!credentials) {
        return res.status(404).json({
          message: "No MassHealth credentials found. Please add them in Settings.",
        });
      }

      const seleniumResult = await callPythonSync("/mh-payment-check", {
        data: {
          massdhpUsername: credentials.username,
          massdhpPassword: credentials.password,
          claimNumber,
        },
      });

      const mhPaidAmount = seleniumResult?.mhPaidAmount ?? 0;

      const updated = await prisma.payment.update({
        where: { id: paymentId },
        data: { mhPaidAmount, updatedById: userId },
      });

      return res.json({ ...updated, mhPaidAmount: Number(updated.mhPaidAmount) });
    } catch (err: unknown) {
      const message = err instanceof Error ? err.message : "MH payment check failed";
      return res.status(500).json({ message });
    }
  }
);

// DELETE /api/payments/:id
router.delete("/:id", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const id = parseIntOrError(req.params.id, "Payment ID");

    // Check if payment exists and belongs to this user
    const existingPayment = await storage.getPayment(id);
    if (!existingPayment) {
      return res.status(404).json({ message: "Payment not found" });
    }

    if (existingPayment.userId !== req.user!.id) {
      return res.status(403).json({
        message:
          "Forbidden: Payment belongs to a different user, you can't delete this.",
      });
    }
    await storage.deletePayment(id, userId);

    res.status(200).json({ message: "Payment deleted successfully" });
  } catch (err: unknown) {
    const message =
      err instanceof Error ? err.message : "Failed to delete payment";
    res.status(500).json({ message });
  }
});

export default router;
