import type { Express } from "express";
import type { Server } from "http";
import { storage } from "./storage";
import { insertSettlementSchema, insertExpenseSchema, insertUserSchema } from "@shared/schema";
import { z } from "zod";
import { api } from "@shared/routes";
import { ExcelService } from "./excel-service";
import { PdfService } from "./pdf-service";

async function getSplitConfig() {
  const settings = await storage.getSettings();
  return {
    cutoffDate: settings.cutoff_date || '2026-02-08',
    boneyRate: parseFloat(settings.boney_rate || '0.05'),
    before: {
      partyA: parseFloat(settings.before_party_a_rate || '0.30'),
      partyB: parseFloat(settings.before_party_b_rate || '0.65'),
      partyC: parseFloat(settings.before_party_c_rate || '0.05'),
    },
    after: {
      partyA: parseFloat(settings.after_party_a_rate || '0.33'),
      partyB: parseFloat(settings.after_party_b_rate || '0.62'),
      partyC: parseFloat(settings.after_party_c_rate || '0.05'),
    },
  };
}

export async function registerRoutes(
  httpServer: Server,
  app: Express
): Promise<Server> {
  // === API ROUTES ===

  // List Settlements
  app.get(api.settlements.list.path, async (req, res) => {
    const settlements = await storage.getSettlements();
    res.json(settlements);
  });

  // Get Settlement
  app.get(api.settlements.get.path, async (req, res) => {
    const id = parseInt(req.params.id);
    if (isNaN(id)) return res.status(400).json({ message: "Invalid ID" });
    
    const settlement = await storage.getSettlement(id);
    if (!settlement) return res.status(404).json({ message: "Settlement not found" });
    
    res.json(settlement);
  });

  // Create Settlement
  app.post(api.settlements.create.path, async (req, res) => {
    try {
      // Validate input using Zod
      const input = api.settlements.create.input.parse(req.body);
      
      // 1. Sum expenses
      const totalExpenses = input.expenses.reduce((sum, exp) => sum + parseFloat(exp.amount), 0);
      
      // 2. Sum direct payments and calculate total gross revenue
      const directPaymentsTotal = input.directPayments.reduce((sum, dp) => sum + parseFloat(dp.amount), 0);
      const grossIncome = parseFloat(input.grossIncome) + directPaymentsTotal;
      
      // 3. Calculate Net Income
      const feePercentage = parseFloat(input.feePercentage || "0") / 100;
      const paypalFees = input.feePercentage ? parseFloat(input.grossIncome) * feePercentage : parseFloat(input.paypalFees || "0");
      const netIncome = grossIncome - paypalFees - totalExpenses;

      // 4. Detect Date Rule
      const weekEndDate = new Date(input.weekEndDate);
      const splitConfig = await getSplitConfig();
      const cutoffDate = new Date(splitConfig.cutoffDate);
      const isBeforeCutoff = weekEndDate < cutoffDate;
      const rules = isBeforeCutoff ? splitConfig.before : splitConfig.after;

      // 5. Apply Split
      const partyAShare = netIncome * rules.partyA;
      const partyBShare = netIncome * rules.partyB;
      const partyCShare = netIncome * rules.partyC;

      // Boney takes a % from Phoenix and Broduer's shares
      const boneyFromA = partyAShare * splitConfig.boneyRate;
      const boneyFromB = partyBShare * splitConfig.boneyRate;
      const partyDShare = boneyFromA + boneyFromB;

      // 6. Offset direct payments
      const partyADirect = input.directPayments.filter(dp => dp.receivedBy === 'party_a').reduce((sum, dp) => sum + parseFloat(dp.amount), 0);
      const partyBDirect = input.directPayments.filter(dp => dp.receivedBy === 'party_b').reduce((sum, dp) => sum + parseFloat(dp.amount), 0);
      const partyCDirect = input.directPayments.filter(dp => dp.receivedBy === 'party_c').reduce((sum, dp) => sum + parseFloat(dp.amount), 0);
      const partyDDirect = input.directPayments.filter(dp => dp.receivedBy === 'party_d').reduce((sum, dp) => sum + parseFloat(dp.amount), 0);

      const partyAPayout = partyAShare - boneyFromA - partyADirect;
      const partyBPayout = partyBShare - boneyFromB - partyBDirect;
      const partyCPayout = partyCShare - partyCDirect;
      const partyDPayout = partyDShare - partyDDirect;

      // 7. Prepare data for storage
      const settlementData = {
        weekStartDate: input.weekStartDate,
        weekEndDate: input.weekEndDate,
        grossIncome: grossIncome.toFixed(2),
        paypalFees: paypalFees.toFixed(2),
        feePercentage: input.feePercentage || "0",
        notes: input.notes,
        totalExpenses: totalExpenses.toFixed(2),
        directPaymentsTotal: directPaymentsTotal.toFixed(2),
        netIncome: netIncome.toFixed(2),
        partyAShare: partyAShare.toFixed(2),
        partyBShare: partyBShare.toFixed(2),
        partyCShare: partyCShare.toFixed(2),
        partyDShare: partyDShare.toFixed(2),
        partyAPayout: partyAPayout.toFixed(2),
        partyBPayout: partyBPayout.toFixed(2),
        partyCPayout: partyCPayout.toFixed(2),
        partyDPayout: partyDPayout.toFixed(2),
      };

      const expensesData = input.expenses.map(e => ({
        description: e.description,
        amount: e.amount,
        payeeEmail: e.payeeEmail,
        notes: e.notes,
      }));

      const directPaymentsData = input.directPayments.map(dp => ({
        amount: dp.amount,
        currency: dp.currency || 'USD',
        paymentMethod: dp.paymentMethod,
        receivedBy: dp.receivedBy,
        reference: dp.reference,
        notes: dp.notes,
      }));

      // 8. Save to DB
      const settlement = await storage.createSettlement(settlementData, expensesData, directPaymentsData);
      
      res.status(201).json(settlement);

    } catch (err) {
      if (err instanceof z.ZodError) {
        return res.status(400).json({ 
          message: "Validation Error", 
          details: err.errors 
        });
      }
      console.error(err);
      res.status(500).json({ message: "Internal Server Error" });
    }
  });

  // Update Settlement
  app.put(api.settlements.update.path, async (req, res) => {
    try {
      const id = parseInt(req.params.id);
      if (isNaN(id)) return res.status(400).json({ message: "Invalid ID" });

      const input = api.settlements.update.input.parse(req.body);
      
      const totalExpenses = input.expenses.reduce((sum, exp) => sum + parseFloat(exp.amount), 0);
      const directPaymentsTotal = input.directPayments.reduce((sum, dp) => sum + parseFloat(dp.amount), 0);
      const grossIncome = parseFloat(input.grossIncome) + directPaymentsTotal;
      
      const feePercentage = parseFloat(input.feePercentage || "0") / 100;
      const paypalFees = input.feePercentage ? parseFloat(input.grossIncome) * feePercentage : parseFloat(input.paypalFees || "0");
      const netIncome = grossIncome - paypalFees - totalExpenses;

      const weekEndDate = new Date(input.weekEndDate);
      const splitConfig = await getSplitConfig();
      const cutoffDate = new Date(splitConfig.cutoffDate);
      const rules = weekEndDate < cutoffDate ? splitConfig.before : splitConfig.after;

      const partyAShare = netIncome * rules.partyA;
      const partyBShare = netIncome * rules.partyB;
      const partyCShare = netIncome * rules.partyC;

      const boneyFromA = partyAShare * splitConfig.boneyRate;
      const boneyFromB = partyBShare * splitConfig.boneyRate;
      const partyDShare = boneyFromA + boneyFromB;

      const partyADirect = input.directPayments.filter(dp => dp.receivedBy === 'party_a').reduce((sum, dp) => sum + parseFloat(dp.amount), 0);
      const partyBDirect = input.directPayments.filter(dp => dp.receivedBy === 'party_b').reduce((sum, dp) => sum + parseFloat(dp.amount), 0);
      const partyCDirect = input.directPayments.filter(dp => dp.receivedBy === 'party_c').reduce((sum, dp) => sum + parseFloat(dp.amount), 0);
      const partyDDirect = input.directPayments.filter(dp => dp.receivedBy === 'party_d').reduce((sum, dp) => sum + parseFloat(dp.amount), 0);

      const partyAPayout = partyAShare - boneyFromA - partyADirect;
      const partyBPayout = partyBShare - boneyFromB - partyBDirect;
      const partyCPayout = partyCShare - partyCDirect;
      const partyDPayout = partyDShare - partyDDirect;

      const settlementData = {
        weekStartDate: input.weekStartDate,
        weekEndDate: input.weekEndDate,
        grossIncome: grossIncome.toFixed(2),
        paypalFees: paypalFees.toFixed(2),
        feePercentage: input.feePercentage || "0",
        notes: input.notes,
        totalExpenses: totalExpenses.toFixed(2),
        directPaymentsTotal: directPaymentsTotal.toFixed(2),
        netIncome: netIncome.toFixed(2),
        partyAShare: partyAShare.toFixed(2),
        partyBShare: partyBShare.toFixed(2),
        partyCShare: partyCShare.toFixed(2),
        partyDShare: partyDShare.toFixed(2),
        partyAPayout: partyAPayout.toFixed(2),
        partyBPayout: partyBPayout.toFixed(2),
        partyCPayout: partyCPayout.toFixed(2),
        partyDPayout: partyDPayout.toFixed(2),
      };

      const expensesData = input.expenses.map(e => ({
        description: e.description,
        amount: e.amount,
        payeeEmail: e.payeeEmail,
        notes: e.notes,
      }));

      const directPaymentsData = input.directPayments.map(dp => ({
        amount: dp.amount,
        currency: dp.currency || 'USD',
        paymentMethod: dp.paymentMethod,
        receivedBy: dp.receivedBy,
        reference: dp.reference,
        notes: dp.notes,
      }));

      const settlement = await storage.updateSettlement(id, settlementData, expensesData, directPaymentsData);
      res.json(settlement);
    } catch (err) {
      if (err instanceof z.ZodError) {
        return res.status(400).json({ message: "Validation Error", details: err.errors });
      }
      console.error(err);
      res.status(500).json({ message: "Internal Server Error" });
    }
  });

  // Delete Settlement
  app.delete(api.settlements.delete.path, async (req, res) => {
    const id = parseInt(req.params.id);
    if (isNaN(id)) return res.status(400).json({ message: "Invalid ID" });

    await storage.deleteSettlement(id);
    res.status(204).send();
  });

  // Export Settlement
  app.get(api.settlements.export.path, async (req, res) => {
    const id = parseInt(req.params.id);
    if (isNaN(id)) return res.status(400).json({ message: "Invalid ID" });

    const settlement = await storage.getSettlement(id);
    if (!settlement) return res.status(404).json({ message: "Settlement not found" });

    try {
      const buffer = await ExcelService.generateSettlementReport(settlement);
      
      res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
      res.setHeader('Content-Disposition', `attachment; filename=settlement_${settlement.weekEndDate}.xlsx`);
      
      res.send(buffer);
    } catch (error) {
      console.error("Export failed:", error);
      res.status(500).json({ message: "Export failed" });
    }
  });

  // Export Settlement PDF
  app.get(api.settlements.exportPdf.path, async (req, res) => {
    const id = parseInt(req.params.id);
    if (isNaN(id)) return res.status(400).json({ message: "Invalid ID" });

    const settlement = await storage.getSettlement(id);
    if (!settlement) return res.status(404).json({ message: "Settlement not found" });

    try {
      const buffer = await PdfService.generateSettlementPdf(settlement);
      
      res.setHeader('Content-Type', 'application/pdf');
      res.setHeader('Content-Disposition', `attachment; filename=settlement_${settlement.weekEndDate}.pdf`);
      
      res.send(buffer);
    } catch (error) {
      console.error("PDF Export failed:", error);
      res.status(500).json({ message: "PDF Export failed" });
    }
  });

  // === SETTINGS ROUTES ===

  // Get all settings
  app.get(api.settings.get.path, async (req, res) => {
    const settings = await storage.getSettings();
    res.json(settings);
  });

  // Update settings
  app.put(api.settings.update.path, async (req, res) => {
    try {
      const input = api.settings.update.input.parse(req.body);
      const updated = await storage.upsertSettings(input);
      res.json(updated);
    } catch (err) {
      if (err instanceof z.ZodError) {
        return res.status(400).json({ message: "Validation Error", details: err.errors });
      }
      console.error(err);
      res.status(500).json({ message: "Internal Server Error" });
    }
  });

  // === USERS ROUTES ===

  // List users
  app.get(api.users.list.path, async (req, res) => {
    const usersList = await storage.getUsers();
    res.json(usersList);
  });

  // Create user
  app.post(api.users.create.path, async (req, res) => {
    try {
      const input = insertUserSchema.parse(req.body);
      const user = await storage.createUser(input);
      res.status(201).json(user);
    } catch (err) {
      if (err instanceof z.ZodError) {
        return res.status(400).json({ message: "Validation Error", details: err.errors });
      }
      console.error(err);
      res.status(500).json({ message: "Internal Server Error" });
    }
  });

  // Update user
  app.put(api.users.update.path, async (req, res) => {
    try {
      const id = parseInt(req.params.id);
      if (isNaN(id)) return res.status(400).json({ message: "Invalid ID" });
      const input = insertUserSchema.partial().parse(req.body);
      const user = await storage.updateUser(id, input);
      res.json(user);
    } catch (err) {
      if (err instanceof z.ZodError) {
        return res.status(400).json({ message: "Validation Error", details: err.errors });
      }
      if ((err as Error).message === "User not found") {
        return res.status(404).json({ message: "User not found" });
      }
      console.error(err);
      res.status(500).json({ message: "Internal Server Error" });
    }
  });

  // Delete user
  app.delete(api.users.delete.path, async (req, res) => {
    const id = parseInt(req.params.id);
    if (isNaN(id)) return res.status(400).json({ message: "Invalid ID" });
    await storage.deleteUser(id);
    res.status(204).send();
  });

  return httpServer;
}
