/**
 * Tests for `src/lib/water-log-reporting.ts` — pure-function utilities
 * used by both the admin and field clients. Covers:
 *   - irrigation season detection (in/out + year-wrapping)
 *   - entry shaping for the report format
 *   - filter by date range, headgate, user, submission source
 *   - CSV encoding (commas, quotes, newlines, unicode)
 *   - report formatter ("Daily Report" output for SMS/email)
 */
import { describe, it, expect } from "vitest";
import {
  isInIrrigationSeason,
  shapeWaterLogEntry,
  filterWaterLogEntries,
  waterLogToCSV,
  formatDailyWaterReport,
  getDisplayAgeLabel,
  getDisplayAgeColor,
  type IrrigationSeasonSettings,
  type WaterLogReportRow,
} from "@/lib/water-log-reporting";

const ROW = (overrides: Partial<WaterLogReportRow> = {}): WaterLogReportRow => ({
  logged_at: "2026-06-15T14:30:00.000Z",
  headgate_name: "Upper Ditch",
  user_name: "Tyler",
  user_role: "irrigator",
  measurement: 3.2,
  unit: "CFS",
  notes: null,
  submitted_via: "field",
  ...overrides,
});

describe("isInIrrigationSeason", () => {
  const season: IrrigationSeasonSettings = {
    seasonStartMonth: 3,
    seasonStartDay: 15,
    seasonEndMonth: 10,
    seasonEndDay: 15,
  };

  // Construct dates in *local* time (year, monthIndex, day) so the test
  // is timezone-independent. `new Date("2026-03-15")` would parse as
  // UTC midnight, which on a US TZ tester becomes the previous day
  // locally and makes boundary assertions brittle.
  const localDate = (y: number, m: number, d: number) => new Date(y, m - 1, d);

  it("returns true inside the season (May 1)", () => {
    expect(isInIrrigationSeason(localDate(2026, 5, 1), season)).toBe(true);
  });

  it("returns false before the season (Feb 1)", () => {
    expect(isInIrrigationSeason(localDate(2026, 2, 1), season)).toBe(false);
  });

  it("returns false after the season (Dec 1)", () => {
    expect(isInIrrigationSeason(localDate(2026, 12, 1), season)).toBe(false);
  });

  it("returns true on the boundary day (Mar 15)", () => {
    expect(isInIrrigationSeason(localDate(2026, 3, 15), season)).toBe(true);
  });

  it("returns true on the closing boundary (Oct 15)", () => {
    expect(isInIrrigationSeason(localDate(2026, 10, 15), season)).toBe(true);
  });

  it("returns false the day after closing (Oct 16)", () => {
    expect(isInIrrigationSeason(localDate(2026, 10, 16), season)).toBe(false);
  });

  it("handles a season that wraps the year (Nov → Feb)", () => {
    const wrap: IrrigationSeasonSettings = {
      seasonStartMonth: 11,
      seasonStartDay: 1,
      seasonEndMonth: 2,
      seasonEndDay: 28,
    };
    expect(isInIrrigationSeason(localDate(2026, 12, 25), wrap)).toBe(true);
    expect(isInIrrigationSeason(localDate(2026, 1, 15), wrap)).toBe(true);
    expect(isInIrrigationSeason(localDate(2026, 6, 1), wrap)).toBe(false);
  });

  it("uses default settings (Mar 15 → Oct 15) when none provided", () => {
    expect(isInIrrigationSeason(localDate(2026, 4, 1))).toBe(true);
    expect(isInIrrigationSeason(localDate(2026, 12, 1))).toBe(false);
  });
});

describe("shapeWaterLogEntry", () => {
  it("normalizes an entry, providing a default role", () => {
    const out = shapeWaterLogEntry({
      id: "x",
      logged_at: "2026-06-15T14:30:00.000Z",
      headgate_name: "Upper Ditch",
      user_name: "Tyler",
      user_role: "water_admin",
      measurement: 3.2,
      unit: "CFS",
      notes: null,
      submitted_via: "field",
    });
    expect(out).toEqual({
      logged_at: "2026-06-15T14:30:00.000Z",
      headgate_name: "Upper Ditch",
      user_name: "Tyler",
      user_role: "water_admin",
      measurement: 3.2,
      unit: "CFS",
      notes: null,
      submitted_via: "field",
    });
  });

  it("defaults role to 'irrigator' when missing", () => {
    const out = shapeWaterLogEntry({
      id: "x",
      logged_at: "2026-06-15T14:30:00.000Z",
      headgate_name: "Upper Ditch",
      user_name: "Tyler",
      measurement: 3.2,
      unit: "CFS",
      notes: null,
      submitted_via: "field",
    });
    expect(out.user_role).toBe("irrigator");
  });
});

describe("filterWaterLogEntries", () => {
  const rows: WaterLogReportRow[] = [
    ROW({ logged_at: "2026-06-10T10:00:00.000Z", headgate_name: "A", user_name: "Tyler" }),
    ROW({ logged_at: "2026-06-15T10:00:00.000Z", headgate_name: "B", user_name: "Tyler" }),
    ROW({ logged_at: "2026-06-20T10:00:00.000Z", headgate_name: "A", user_name: "Sam" }),
  ];

  it("returns everything when no filters", () => {
    expect(filterWaterLogEntries(rows, {})).toHaveLength(3);
  });

  it("filters by dateFrom (inclusive of full day)", () => {
    const out = filterWaterLogEntries(rows, { dateFrom: "2026-06-15" });
    expect(out).toHaveLength(2);
  });

  it("filters by dateTo (inclusive of full day)", () => {
    const out = filterWaterLogEntries(rows, { dateTo: "2026-06-15" });
    expect(out).toHaveLength(2);
  });

  it("filters by date range", () => {
    const out = filterWaterLogEntries(rows, {
      dateFrom: "2026-06-15",
      dateTo: "2026-06-15",
    });
    expect(out).toHaveLength(1);
  });

  it("filters by headgate", () => {
    const out = filterWaterLogEntries(rows, { headgateId: "A" });
    expect(out).toHaveLength(2);
  });

  it("filters by user", () => {
    const out = filterWaterLogEntries(rows, { userId: "Sam" });
    expect(out).toHaveLength(1);
  });

  it("filters by submission source", () => {
    const rows2 = [
      ROW({ submitted_via: "field" }),
      ROW({ submitted_via: "admin" }),
    ];
    expect(filterWaterLogEntries(rows2, { submittedVia: "admin" })).toHaveLength(1);
  });

  it("combines multiple filters", () => {
    const out = filterWaterLogEntries(rows, {
      dateFrom: "2026-06-15",
      headgateId: "A",
    });
    expect(out).toHaveLength(1);
    expect(out[0].user_name).toBe("Sam");
  });
});

describe("waterLogToCSV", () => {
  it("emits a header row + one row per entry", () => {
    const csv = waterLogToCSV([
      ROW(),
      ROW({ measurement: 4.5, notes: "rain" }),
    ]);
    const lines = csv.split("\n");
    expect(lines).toHaveLength(3);
    expect(lines[0]).toContain("When");
    expect(lines[0]).toContain("Headgate");
  });

  it("quotes fields containing commas, quotes, or newlines", () => {
    const csv = waterLogToCSV([
      ROW({ notes: 'has, "quotes" and\nnewlines' }),
    ]);
    // The cell value is properly quoted with internal quotes doubled.
    // An embedded newline splits the row across two physical lines,
    // so we read the full CSV string rather than a single line.
    expect(csv).toContain('"has, ""quotes"" and\nnewlines"');
  });

  it("escapes unicode and emoji", () => {
    const csv = waterLogToCSV([
      ROW({ user_name: "José 🚜" }),
    ]);
    expect(csv).toContain("José 🚜");
  });

  it("emits a syntactically valid CSV (parses with a basic split)", () => {
    const csv = waterLogToCSV([ROW(), ROW()]);
    // 1 header + 2 data rows
    expect(csv.split("\n")).toHaveLength(3);
  });
});

describe("formatDailyWaterReport", () => {
  it("returns null when no rows and sendEvenIfEmpty is false", () => {
    expect(formatDailyWaterReport([])).toBeNull();
  });

  it("emits an empty-day message when sendEvenIfEmpty is true", () => {
    const out = formatDailyWaterReport([], { sendEvenIfEmpty: true });
    expect(out).toContain("No entries reported today");
  });

  it("includes the recipient name when provided", () => {
    const out = formatDailyWaterReport([ROW()], { recipientName: "David" });
    // We don't enforce the salutation format, but the row must be present.
    expect(out).toContain("Tyler");
    expect(out).toContain("Upper Ditch");
  });

  it("sums the total measurement", () => {
    const out = formatDailyWaterReport([
      ROW({ measurement: 2 }),
      ROW({ measurement: 3.5 }),
    ]);
    expect(out).toContain("5.50");
  });

  it("includes notes when present", () => {
    const out = formatDailyWaterReport([ROW({ notes: "Ditch running heavy" })]);
    expect(out).toContain("Ditch running heavy");
  });
});

describe("display age helpers", () => {
  it("returns 'never' for null", () => {
    expect(getDisplayAgeLabel(null)).toBe("never");
    expect(getDisplayAgeColor(null)).toBe("red");
  });

  it("returns 'just now' under 1 minute", () => {
    expect(getDisplayAgeLabel(0.5)).toBe("just now");
    expect(getDisplayAgeColor(0.5)).toBe("green");
  });

  it("buckets by minutes / hours / days", () => {
    expect(getDisplayAgeLabel(15)).toBe("15m ago");
    expect(getDisplayAgeLabel(90)).toBe("1h ago");
    expect(getDisplayAgeLabel(120)).toBe("2h ago");
    expect(getDisplayAgeLabel(60 * 26)).toBe("1d ago");
  });

  it("colors by recency", () => {
    expect(getDisplayAgeColor(15)).toBe("green"); // < 30m
    expect(getDisplayAgeColor(60)).toBe("yellow"); // < 2h
    expect(getDisplayAgeColor(180)).toBe("red"); // > 2h
  });
});
