import { mutation } from "../_generated/server";
import { v } from "convex/values";
import {
  sanitizeString,
  sanitizeOptionalString,
} from "../helpers/sanitize";

/**
 * Create a new quotation
 * Phase 7: Leads, Quotations, Expenses
 */
export const createQuotation = mutation({
  args: {
    userId: v.string(),
    customerId: v.optional(v.string()),
    title: v.string(),
    notes: v.optional(v.string()),
    currency: v.optional(v.string()),
    taxRate: v.optional(v.number()),
    discountRate: v.optional(v.number()),
    validUntil: v.optional(v.number()),
    items: v.optional(
      v.array(
        v.object({
          description: v.string(),
          quantity: v.number(),
          unitPrice: v.number(),
        }),
      ),
    ),
  },
  handler: async (ctx, args) => {
    // Auth check — API layer validates ownership; warn if no identity in Convex
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      console.warn(
        "createQuotation called without Convex auth identity — relying on API-layer auth",
      );
    }
    const now = Date.now();
    // Generate a quotation number: Q-YYYYMMDD-XXXXX
    const dateStr = new Date(now).toISOString().slice(0, 10).replace(/-/g, "");
    const random = Math.random().toString(36).substring(2, 7).toUpperCase();
    const quotationNumber = `Q-${dateStr}-${random}`;

    const taxRate = args.taxRate ?? 0;
    const discountRate = args.discountRate ?? 0;

    // Calculate totals from items
    const items = args.items ?? [];
    const subtotal = items.reduce(
      (sum, item) => sum + item.quantity * item.unitPrice,
      0,
    );
    const discountAmount = subtotal * (discountRate / 100);
    const taxableAmount = subtotal - discountAmount;
    const taxAmount = taxableAmount * (taxRate / 100);
    const total = taxableAmount + taxAmount;

    // Generate a unique portal token for public access
    const portalToken = `${random}${Math.random().toString(36).substring(2, 10)}`.toLowerCase();

    const id = await ctx.db.insert("quotations", {
      userId: args.userId,
      customerId: args.customerId,
      quotationNumber,
      title: sanitizeString(args.title, 200),
      notes: sanitizeOptionalString(args.notes, 5000),
      status: "SENT",
      portalToken,
      currency: args.currency ?? "QAR",
      subtotal,
      taxRate,
      taxAmount,
      discountRate,
      discountAmount,
      total,
      validUntil: args.validUntil,
      paymentStatus: "UNPAID",
      amountPaid: 0,
      penaltyAmount: 0,
      createdAt: now,
      updatedAt: now,
    });

    // Insert line items
    for (let i = 0; i < items.length; i++) {
      const item = items[i];
      await ctx.db.insert("quotation_items", {
        quotationId: id,
        description: sanitizeString(item.description, 1000),
        quantity: item.quantity,
        unitPrice: item.unitPrice,
        total: item.quantity * item.unitPrice,
        sortOrder: i,
        createdAt: now,
        updatedAt: now,
      });
    }

    return id;
  },
});

/**
 * Update quotation details and status
 * Phase 7: Leads, Quotations, Expenses
 */
export const updateQuotation = mutation({
  args: {
    id: v.id("quotations"),
    title: v.optional(v.string()),
    notes: v.optional(v.string()),
    status: v.optional(v.string()),
    customerId: v.optional(v.string()),
    taxRate: v.optional(v.number()),
    discountRate: v.optional(v.number()),
    validUntil: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    const { id, ...fields } = args;
    const updates: Record<string, unknown> = { updatedAt: Date.now() };

    if (fields.title !== undefined) updates.title = sanitizeString(fields.title, 200);
    if (fields.notes !== undefined) updates.notes = sanitizeString(fields.notes, 5000);
    if (fields.status !== undefined) updates.status = fields.status;
    if (fields.customerId !== undefined) updates.customerId = fields.customerId;
    if (fields.taxRate !== undefined) updates.taxRate = fields.taxRate;
    if (fields.discountRate !== undefined)
      updates.discountRate = fields.discountRate;
    if (fields.validUntil !== undefined) updates.validUntil = fields.validUntil;

    // If tax or discount rate changed, recalculate totals
    if (fields.taxRate !== undefined || fields.discountRate !== undefined) {
      const quotation = await ctx.db.get(id);
      if (quotation) {
        const taxRate = fields.taxRate ?? quotation.taxRate;
        const discountRate = fields.discountRate ?? quotation.discountRate;
        const subtotal = quotation.subtotal;
        const discountAmount = subtotal * (discountRate / 100);
        const taxableAmount = subtotal - discountAmount;
        const taxAmount = taxableAmount * (taxRate / 100);
        const total = taxableAmount + taxAmount;

        updates.taxRate = taxRate;
        updates.discountRate = discountRate;
        updates.discountAmount = discountAmount;
        updates.taxAmount = taxAmount;
        updates.total = total;
      }
    }

    await ctx.db.patch(id, updates);
    return id;
  },
});

/**
 * Send a quotation (transition status to SENT)
 * Phase 7: Leads, Quotations, Expenses
 */
export const sendQuotation = mutation({
  args: {
    id: v.id("quotations"),
  },
  handler: async (ctx, args) => {
    const now = Date.now();
    await ctx.db.patch(args.id, {
      status: "SENT",
      sentAt: now,
      updatedAt: now,
    });
    return args.id;
  },
});

/**
 * Accept a quotation
 * Phase 7: Leads, Quotations, Expenses
 */
export const acceptQuotation = mutation({
  args: {
    id: v.id("quotations"),
  },
  handler: async (ctx, args) => {
    const now = Date.now();
    await ctx.db.patch(args.id, {
      status: "ACCEPTED",
      respondedAt: now,
      updatedAt: now,
    });
    return args.id;
  },
});

/**
 * Reject a quotation
 * Phase 7: Leads, Quotations, Expenses
 */
export const rejectQuotation = mutation({
  args: {
    id: v.id("quotations"),
  },
  handler: async (ctx, args) => {
    const now = Date.now();
    await ctx.db.patch(args.id, {
      status: "DECLINED",
      respondedAt: now,
      updatedAt: now,
    });
    return args.id;
  },
});

/**
 * Add a line item to a quotation
 * Phase 7: Leads, Quotations, Expenses
 */
export const addItem = mutation({
  args: {
    quotationId: v.string(),
    description: v.string(),
    quantity: v.number(),
    unitPrice: v.number(),
    sortOrder: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    const now = Date.now();
    const lineTotal = args.quantity * args.unitPrice;

    // Determine sort order if not provided
    let sortOrder = args.sortOrder ?? 0;
    if (args.sortOrder === undefined) {
      const existingItems = await ctx.db
        .query("quotation_items")
        .withIndex("by_quotationId", (q) =>
          q.eq("quotationId", args.quotationId),
        )
        .collect();
      sortOrder = existingItems.length;
    }

    // Insert the line item
    const itemId = await ctx.db.insert("quotation_items", {
      quotationId: args.quotationId,
      description: sanitizeString(args.description, 1000),
      quantity: args.quantity,
      unitPrice: args.unitPrice,
      total: lineTotal,
      sortOrder,
      createdAt: now,
      updatedAt: now,
    });

    // Recalculate quotation totals
    const allItems = await ctx.db
      .query("quotation_items")
      .withIndex("by_quotationId", (q) => q.eq("quotationId", args.quotationId))
      .collect();

    const subtotal = allItems.reduce((sum, item) => sum + item.total, 0);

    // Get the quotation to read tax/discount rates
    // quotationId is the Convex document ID as a string
    const quotation = await ctx.db.get(args.quotationId as never);
    if (quotation) {
      const discountAmount =
        subtotal * ((quotation as { discountRate: number }).discountRate / 100);
      const taxableAmount = subtotal - discountAmount;
      const taxAmount =
        taxableAmount * ((quotation as { taxRate: number }).taxRate / 100);
      const total = taxableAmount + taxAmount;

      await ctx.db.patch(args.quotationId as never, {
        subtotal,
        discountAmount,
        taxAmount,
        total,
        updatedAt: now,
      });
    }

    return itemId;
  },
});

/**
 * Remove a line item from a quotation
 * Phase 7: Leads, Quotations, Expenses
 */
export const removeItem = mutation({
  args: {
    id: v.id("quotation_items"),
  },
  handler: async (ctx, args) => {
    const item = await ctx.db.get(args.id);
    if (!item) return null;

    const quotationId = item.quotationId;
    const now = Date.now();

    // Delete the item
    await ctx.db.delete(args.id);

    // Recalculate quotation totals
    const remainingItems = await ctx.db
      .query("quotation_items")
      .withIndex("by_quotationId", (q) => q.eq("quotationId", quotationId))
      .collect();

    const subtotal = remainingItems.reduce((sum, i) => sum + i.total, 0);

    const quotation = await ctx.db.get(quotationId as never);
    if (quotation) {
      const discountAmount =
        subtotal * ((quotation as { discountRate: number }).discountRate / 100);
      const taxableAmount = subtotal - discountAmount;
      const taxAmount =
        taxableAmount * ((quotation as { taxRate: number }).taxRate / 100);
      const total = taxableAmount + taxAmount;

      await ctx.db.patch(quotationId as never, {
        subtotal,
        discountAmount,
        taxAmount,
        total,
        updatedAt: now,
      });
    }

    return args.id;
  },
});

/**
 * Upsert quotation settings for a user
 * WU-07: Quotation core routes wiring
 */
export const upsertSettings = mutation({
  args: {
    userId: v.string(),
    defaultCurrency: v.optional(v.string()),
    defaultTaxRate: v.optional(v.number()),
    defaultPaymentTerms: v.optional(v.number()),
    defaultValidDays: v.optional(v.number()),
    penaltyEnabled: v.optional(v.boolean()),
    penaltyType: v.optional(
      v.union(v.literal("PERCENTAGE"), v.literal("FLAT")),
    ),
    penaltyRate: v.optional(v.number()),
    penaltyGraceDays: v.optional(v.number()),
    penaltyCompound: v.optional(v.boolean()),
    penaltyMaxAmount: v.optional(v.number()),
    penaltyFrequencyDays: v.optional(v.number()),
    reminderEnabled: v.optional(v.boolean()),
    reminderChannel: v.optional(v.string()),
    reminderSchedule: v.optional(v.any()),
    companyName: v.optional(v.string()),
    companyLogo: v.optional(v.string()),
    footerText: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    // Auth check — API layer validates ownership; warn if no identity in Convex
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      console.warn(
        "upsertSettings called without Convex auth identity — relying on API-layer auth",
      );
    }
    const { userId, ...fields } = args;
    const now = Date.now();

    const existing = await ctx.db
      .query("quotation_settings")
      .withIndex("by_userId", (q) => q.eq("userId", userId))
      .first();

    // Sanitize user-provided text fields
    if (fields.companyName !== undefined)
      fields.companyName = sanitizeOptionalString(fields.companyName, 200);
    if (fields.footerText !== undefined)
      fields.footerText = sanitizeOptionalString(fields.footerText, 2000);
    if (fields.reminderChannel !== undefined)
      fields.reminderChannel = sanitizeOptionalString(fields.reminderChannel, 100);

    if (existing) {
      // Update only provided fields
      const updates: Record<string, unknown> = { updatedAt: now };
      for (const [key, value] of Object.entries(fields)) {
        if (value !== undefined) updates[key] = value;
      }
      await ctx.db.patch(existing._id, updates);
      return { ...existing, ...updates };
    }

    // Create with defaults
    const data = {
      userId,
      defaultCurrency: fields.defaultCurrency ?? "QAR",
      defaultTaxRate: fields.defaultTaxRate ?? 0,
      defaultPaymentTerms: fields.defaultPaymentTerms ?? 30,
      defaultValidDays: fields.defaultValidDays ?? 30,
      penaltyEnabled: fields.penaltyEnabled ?? false,
      penaltyType: (fields.penaltyType ?? "PERCENTAGE") as
        | "PERCENTAGE"
        | "FLAT",
      penaltyRate: fields.penaltyRate ?? 0,
      penaltyGraceDays: fields.penaltyGraceDays ?? 0,
      penaltyCompound: fields.penaltyCompound ?? false,
      penaltyMaxAmount: fields.penaltyMaxAmount,
      penaltyFrequencyDays: fields.penaltyFrequencyDays ?? 30,
      reminderEnabled: fields.reminderEnabled ?? false,
      reminderChannel: fields.reminderChannel ?? "email",
      reminderSchedule: fields.reminderSchedule ?? [],
      companyName: fields.companyName,
      companyLogo: fields.companyLogo,
      footerText: fields.footerText,
      createdAt: now,
      updatedAt: now,
    };
    const id = await ctx.db.insert("quotation_settings", data);
    return { _id: id, ...data };
  },
});

/**
 * Record a payment against a quotation
 * Phase 7: Leads, Quotations, Expenses
 */
export const recordPayment = mutation({
  args: {
    quotationId: v.string(),
    amount: v.number(),
    currency: v.optional(v.string()),
    method: v.optional(v.string()),
    stripePaymentIntentId: v.optional(v.string()),
    reference: v.optional(v.string()),
    manualNotes: v.optional(v.string()),
    recordedBy: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const now = Date.now();

    // Insert payment record
    const paymentId = await ctx.db.insert("quotation_payments", {
      quotationId: args.quotationId,
      amount: args.amount,
      currency: args.currency ?? "QAR",
      method: (args.method ?? "MANUAL_OTHER") as
        | "STRIPE"
        | "MANUAL_CASH"
        | "MANUAL_BANK_TRANSFER"
        | "MANUAL_CHECK"
        | "MANUAL_OTHER",
      stripePaymentIntentId: args.stripePaymentIntentId,
      reference: args.reference,
      manualNotes: sanitizeOptionalString(args.manualNotes, 5000),
      recordedBy: args.recordedBy,
      paidAt: now,
      createdAt: now,
    });

    // Update quotation payment status
    const quotation = await ctx.db.get(args.quotationId as never);
    if (quotation) {
      const q = quotation as { amountPaid: number; total: number };
      const newAmountPaid = q.amountPaid + args.amount;
      const paymentStatus = newAmountPaid >= q.total ? "PAID" : "PARTIAL";

      await ctx.db.patch(args.quotationId as never, {
        amountPaid: newAmountPaid,
        paymentStatus,
        paidAt: paymentStatus === "PAID" ? now : undefined,
        updatedAt: now,
      });
    }

    return paymentId;
  },
});

// ═══════════════════════════════════════════════════════════════════════
// Recurring Quotation Mutations — Phase 7 gap-fill
// ═══════════════════════════════════════════════════════════════════════

/**
 * Create a recurring quotation schedule
 */
export const createRecurring = mutation({
  args: {
    userId: v.string(),
    customerId: v.optional(v.string()),
    title: v.string(),
    notes: v.optional(v.string()),
    currency: v.optional(v.string()),
    taxRate: v.optional(v.number()),
    discountRate: v.optional(v.number()),
    validDays: v.optional(v.number()),
    paymentDueDays: v.optional(v.number()),
    frequency: v.string(),
    customIntervalDays: v.optional(v.number()),
    startDate: v.number(),
    endDate: v.optional(v.number()),
    maxOccurrences: v.optional(v.number()),
    autoSend: v.optional(v.boolean()),
    items: v.array(
      v.object({
        description: v.string(),
        quantity: v.number(),
        unitPrice: v.number(),
      }),
    ),
  },
  handler: async (ctx, args) => {
    // Auth check — API layer validates ownership; warn if no identity in Convex
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      console.warn(
        "createRecurring called without Convex auth identity — relying on API-layer auth",
      );
    }
    const now = Date.now();

    const templateId = await ctx.db.insert("quotation_recurring_templates", {
      userId: args.userId,
      customerId: args.customerId,
      title: sanitizeString(args.title, 200),
      notes: sanitizeOptionalString(args.notes, 5000),
      currency: args.currency ?? "QAR",
      taxRate: args.taxRate ?? 0,
      discountRate: args.discountRate ?? 0,
      validDays: args.validDays ?? 30,
      paymentDueDays: args.paymentDueDays ?? 30,
      frequency: args.frequency as
        | "MONTHLY"
        | "QUARTERLY"
        | "SEMI_ANNUAL"
        | "ANNUAL"
        | "CUSTOM",
      customIntervalDays: args.customIntervalDays,
      status: "ACTIVE",
      startDate: args.startDate,
      nextRunDate: args.startDate,
      endDate: args.endDate,
      maxOccurrences: args.maxOccurrences,
      occurrenceCount: 0,
      autoSend: args.autoSend ?? true,
      createdAt: now,
      updatedAt: now,
    });

    for (let i = 0; i < args.items.length; i++) {
      const item = args.items[i];
      await ctx.db.insert("quotation_recurring_template_items", {
        templateId: templateId as unknown as string,
        description: sanitizeString(item.description, 1000),
        quantity: item.quantity,
        unitPrice: item.unitPrice,
        total: Math.round(item.quantity * item.unitPrice * 100) / 100,
        sortOrder: i,
        createdAt: now,
        updatedAt: now,
      });
    }

    return templateId;
  },
});

/**
 * Update a recurring quotation schedule
 */
export const updateRecurring = mutation({
  args: {
    id: v.id("quotation_recurring_templates"),
    userId: v.string(),
    customerId: v.optional(v.string()),
    title: v.optional(v.string()),
    notes: v.optional(v.string()),
    currency: v.optional(v.string()),
    taxRate: v.optional(v.number()),
    discountRate: v.optional(v.number()),
    validDays: v.optional(v.number()),
    paymentDueDays: v.optional(v.number()),
    frequency: v.optional(v.string()),
    customIntervalDays: v.optional(v.number()),
    endDate: v.optional(v.number()),
    maxOccurrences: v.optional(v.number()),
    autoSend: v.optional(v.boolean()),
    items: v.optional(
      v.array(
        v.object({
          description: v.string(),
          quantity: v.number(),
          unitPrice: v.number(),
        }),
      ),
    ),
  },
  handler: async (ctx, args) => {
    // Auth check — API layer validates ownership; warn if no identity in Convex
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      console.warn(
        "updateRecurring called without Convex auth identity — relying on API-layer auth",
      );
    }
    const existing = await ctx.db.get(args.id);
    if (!existing || existing.userId !== args.userId) return null;

    const now = Date.now();
    const updates: Record<string, unknown> = { updatedAt: now };

    if (args.customerId !== undefined) updates.customerId = args.customerId;
    if (args.title !== undefined) updates.title = sanitizeString(args.title, 200);
    if (args.notes !== undefined) updates.notes = sanitizeString(args.notes, 5000);
    if (args.currency !== undefined) updates.currency = args.currency;
    if (args.taxRate !== undefined) updates.taxRate = args.taxRate;
    if (args.discountRate !== undefined)
      updates.discountRate = args.discountRate;
    if (args.validDays !== undefined) updates.validDays = args.validDays;
    if (args.paymentDueDays !== undefined)
      updates.paymentDueDays = args.paymentDueDays;
    if (args.frequency !== undefined) updates.frequency = args.frequency;
    if (args.customIntervalDays !== undefined)
      updates.customIntervalDays = args.customIntervalDays;
    if (args.endDate !== undefined) updates.endDate = args.endDate;
    if (args.maxOccurrences !== undefined)
      updates.maxOccurrences = args.maxOccurrences;
    if (args.autoSend !== undefined) updates.autoSend = args.autoSend;

    await ctx.db.patch(args.id, updates);

    // If items provided, replace all template items
    if (args.items) {
      const existingItems = await ctx.db
        .query("quotation_recurring_template_items")
        .withIndex("by_templateId", (q) =>
          q.eq("templateId", args.id as unknown as string),
        )
        .collect();

      for (const item of existingItems) {
        await ctx.db.delete(item._id);
      }

      for (let i = 0; i < args.items.length; i++) {
        const item = args.items[i];
        await ctx.db.insert("quotation_recurring_template_items", {
          templateId: args.id as unknown as string,
          description: sanitizeString(item.description, 1000),
          quantity: item.quantity,
          unitPrice: item.unitPrice,
          total: Math.round(item.quantity * item.unitPrice * 100) / 100,
          sortOrder: i,
          createdAt: now,
          updatedAt: now,
        });
      }
    }

    return await ctx.db.get(args.id);
  },
});

/**
 * Cancel a recurring quotation schedule
 */
export const cancelRecurring = mutation({
  args: {
    id: v.id("quotation_recurring_templates"),
    userId: v.string(),
  },
  handler: async (ctx, args) => {
    // Auth check — API layer validates ownership; warn if no identity in Convex
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      console.warn(
        "cancelRecurring called without Convex auth identity — relying on API-layer auth",
      );
    }
    const existing = await ctx.db.get(args.id);
    if (!existing || existing.userId !== args.userId) return null;
    if (existing.status !== "ACTIVE" && existing.status !== "PAUSED")
      return null;

    await ctx.db.patch(args.id, {
      status: "CANCELLED",
      updatedAt: Date.now(),
    });

    return await ctx.db.get(args.id);
  },
});

/**
 * Pause a recurring quotation schedule
 */
export const pauseRecurring = mutation({
  args: {
    id: v.id("quotation_recurring_templates"),
    userId: v.string(),
  },
  handler: async (ctx, args) => {
    // Auth check — API layer validates ownership; warn if no identity in Convex
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      console.warn(
        "pauseRecurring called without Convex auth identity — relying on API-layer auth",
      );
    }
    const existing = await ctx.db.get(args.id);
    if (!existing || existing.userId !== args.userId) return null;
    if (existing.status !== "ACTIVE") return null;

    await ctx.db.patch(args.id, {
      status: "PAUSED",
      updatedAt: Date.now(),
    });

    return await ctx.db.get(args.id);
  },
});

/**
 * Resume a paused recurring quotation schedule
 */
export const resumeRecurring = mutation({
  args: {
    id: v.id("quotation_recurring_templates"),
    userId: v.string(),
  },
  handler: async (ctx, args) => {
    // Auth check — API layer validates ownership; warn if no identity in Convex
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      console.warn(
        "resumeRecurring called without Convex auth identity — relying on API-layer auth",
      );
    }
    const existing = await ctx.db.get(args.id);
    if (!existing || existing.userId !== args.userId) return null;
    if (existing.status !== "PAUSED") return null;

    const now = Date.now();
    const frequency = existing.frequency;
    const nowDate = new Date(now);
    let nextRunDate: number;

    switch (frequency) {
      case "MONTHLY":
        nowDate.setMonth(nowDate.getMonth() + 1);
        nextRunDate = nowDate.getTime();
        break;
      case "QUARTERLY":
        nowDate.setMonth(nowDate.getMonth() + 3);
        nextRunDate = nowDate.getTime();
        break;
      case "SEMI_ANNUAL":
        nowDate.setMonth(nowDate.getMonth() + 6);
        nextRunDate = nowDate.getTime();
        break;
      case "ANNUAL":
        nowDate.setFullYear(nowDate.getFullYear() + 1);
        nextRunDate = nowDate.getTime();
        break;
      case "CUSTOM":
        nowDate.setDate(
          nowDate.getDate() + (existing.customIntervalDays ?? 30),
        );
        nextRunDate = nowDate.getTime();
        break;
      default:
        nextRunDate = now;
    }

    await ctx.db.patch(args.id, {
      status: "ACTIVE",
      nextRunDate,
      updatedAt: now,
    });

    return await ctx.db.get(args.id);
  },
});
