import {
  loadConfig,
  classifyVisit,
  classifyVisitFull,
  calculateTiers,
  getISOWeek,
  findProvider,
  type PayMappingConfig,
  type WeekBucket,
} from "./pay-mapping.js";

export interface VisitRecord {
  id: string;
  date: string;
  columnheading: string;
  appointmentType: string;
  appointmentTypeId: number;
  apptstatus: number;
  duration: number;
  patient_name: string;
  cptCodes?: string[];
}

export interface PayrollLine {
  employee_id: string;
  pay_type_name: string;
  quantity: number;
  pay_period_start: string;
  pay_period_end: string;
  memo: string;
  rate: number;
  gross: number;
}

export interface PayrollSummary {
  lines: PayrollLine[];
  total_gross: number;
  provider_summaries: {
    provider: string;
    email: string;
    lines: PayrollLine[];
    total: number;
  }[];
  unmapped_types: string[];
  unmapped_providers: string[];
  skipped_visits: number;
  total_visits: number;
}

export function generatePayroll(
  visits: VisitRecord[],
  startDate: string,
  endDate: string,
  configPath?: string
): PayrollSummary {
  const cfg = loadConfig(configPath);

  const unmappedTypes = new Set<string>();
  const unmappedProviders = new Set<string>();
  let skipped = 0;

  // Accumulate: provider → pay_type → { count, weeklyBreakdown }
  const accum: Map<string, Map<string, { count: number; weeklyVisits: Map<string, number> }>> = new Map();

  for (const visit of visits) {
    const provider = findProvider(visit.columnheading, cfg);
    if (!provider) {
      unmappedProviders.add(visit.columnheading);
      skipped++;
      continue;
    }

    const payTypes = classifyVisitFull(
      visit.appointmentTypeId,
      visit.appointmentType,
      visit.apptstatus,
      cfg,
      visit.cptCodes
    );
    if (payTypes.length === 0) {
      if (cfg.appt_status.excluded.includes(visit.apptstatus)) {
        skipped++;
      } else {
        unmappedTypes.add(`${visit.appointmentType} (${visit.appointmentTypeId})`);
        skipped++;
      }
      continue;
    }

    const provKey = provider.rippling_email;
    if (!accum.has(provKey)) accum.set(provKey, new Map());
    const provMap = accum.get(provKey)!;

    for (const payType of payTypes) {
      if (!provMap.has(payType)) provMap.set(payType, { count: 0, weeklyVisits: new Map() });
      const bucket = provMap.get(payType)!;
      bucket.count++;

      const week = getISOWeek(visit.date);
      bucket.weeklyVisits.set(week, (bucket.weeklyVisits.get(week) ?? 0) + 1);
    }
  }

  const lines: PayrollLine[] = [];
  const providerSummaries: PayrollSummary["provider_summaries"] = [];
  const memo = `AMD-${endDate}`;

  for (const [email, payTypes] of accum) {
    const provider = cfg.providers.find((p) => p.rippling_email === email)!;
    const provLines: PayrollLine[] = [];

    if (provider.tiered) {
      // Tiered therapist: aggregate all therapy visits, then split by tier
      const allTherapyWeekly = new Map<string, number>();
      let totalTherapy = 0;

      for (const [payType, bucket] of payTypes) {
        if (payType.includes("No-Show")) {
          const rate = provider.rates[payType] ?? 0;
          const line: PayrollLine = {
            employee_id: email,
            pay_type_name: payType,
            quantity: bucket.count,
            pay_period_start: startDate,
            pay_period_end: endDate,
            memo,
            rate,
            gross: bucket.count * rate,
          };
          provLines.push(line);
          continue;
        }
        // Merge weekly counts for tier calc
        for (const [week, count] of bucket.weeklyVisits) {
          allTherapyWeekly.set(week, (allTherapyWeekly.get(week) ?? 0) + count);
        }
        totalTherapy += bucket.count;
      }

      if (totalTherapy > 0) {
        const weekBuckets: WeekBucket[] = Array.from(allTherapyWeekly.entries())
          .map(([isoWeek, count]) => ({ isoWeek, count }));
        const { tier1, tier2 } = calculateTiers(weekBuckets, provider.tiered.threshold);

        if (tier1 > 0) {
          const t1Name = provider.tiered.tier1_pay_type;
          const t1Rate = provider.rates[t1Name] ?? 0;
          provLines.push({
            employee_id: email,
            pay_type_name: t1Name,
            quantity: tier1,
            pay_period_start: startDate,
            pay_period_end: endDate,
            memo,
            rate: t1Rate,
            gross: tier1 * t1Rate,
          });
        }
        if (tier2 > 0) {
          const t2Name = provider.tiered.tier2_pay_type;
          const t2Rate = provider.rates[t2Name] ?? 0;
          provLines.push({
            employee_id: email,
            pay_type_name: t2Name,
            quantity: tier2,
            pay_period_start: startDate,
            pay_period_end: endDate,
            memo,
            rate: t2Rate,
            gross: tier2 * t2Rate,
          });
        }
      }
    } else {
      // Non-tiered: direct quantity × rate
      for (const [payType, bucket] of payTypes) {
        const rate = provider.rates[payType] ?? 0;
        provLines.push({
          employee_id: email,
          pay_type_name: payType,
          quantity: bucket.count,
          pay_period_start: startDate,
          pay_period_end: endDate,
          memo,
          rate,
          gross: bucket.count * rate,
        });
      }
    }

    lines.push(...provLines);
    providerSummaries.push({
      provider: provider.amd_column_heading,
      email,
      lines: provLines,
      total: provLines.reduce((sum, l) => sum + l.gross, 0),
    });
  }

  return {
    lines,
    total_gross: lines.reduce((sum, l) => sum + l.gross, 0),
    provider_summaries: providerSummaries,
    unmapped_types: Array.from(unmappedTypes),
    unmapped_providers: Array.from(unmappedProviders),
    skipped_visits: skipped,
    total_visits: visits.length,
  };
}

export function toCSV(lines: PayrollLine[]): string {
  const header = "employee_id,pay_type_name,quantity,pay_period_start,pay_period_end,memo";
  const rows = lines.map((l) =>
    `${l.employee_id},"${l.pay_type_name}",${l.quantity},${l.pay_period_start},${l.pay_period_end},${l.memo}`
  );
  return [header, ...rows].join("\n");
}

export function formatSummary(summary: PayrollSummary): string {
  const parts = [`Payroll Summary: ${summary.total_visits} visits processed, ${summary.skipped_visits} skipped\n`];
  parts.push(`Total Gross: $${summary.total_gross.toFixed(2)}\n`);

  for (const ps of summary.provider_summaries) {
    parts.push(`\n${ps.provider} (${ps.email}): $${ps.total.toFixed(2)}`);
    for (const l of ps.lines) {
      parts.push(`  ${l.pay_type_name}: ${l.quantity} × $${l.rate.toFixed(2)} = $${l.gross.toFixed(2)}`);
    }
  }

  if (summary.unmapped_types.length > 0) {
    parts.push(`\nUnmapped AMD types: ${summary.unmapped_types.join(", ")}`);
  }
  if (summary.unmapped_providers.length > 0) {
    parts.push(`\nUnmapped providers: ${summary.unmapped_providers.join(", ")}`);
  }

  return parts.join("\n");
}
