import React, { useCallback, useMemo } from "react";

import { Button, Stack, TableBody, TableRow } from "@mui/material";

import { useDashboardFilter } from "../contexts/DashboardFilterContext";
import { formatDate } from "../util/format_date";

import { TableCell } from "./Table/TableCell";
import TableHead from "./Table/TableHead";
import TableHeading from "./Table/TableHeading";
import Table from "./Table";

interface DataItem {
  [key: string]: string | number | boolean | Date;
}

interface StatsDataTableProps {
  data: DataItem[];
  dateKey?: string;
  csvFileName?: string;
}

export default function StatsDataTable({ data, dateKey, csvFileName }: StatsDataTableProps) {
  const { filter } = useDashboardFilter();

  const mappedData = useMemo(() => {
    return data
      .map((item) => {
        const dateEntry = getDateEntry(item, dateKey);
        if (dateEntry === undefined) return undefined;

        const [key, date] = dateEntry;
        const { [key]: _, ...rest } = item;

        return { [key]: date, ...rest };
      })
      .filter((item) => item !== undefined);
  }, [data, dateKey]);

  const handleExportToCSV = useCallback(() => {
    if (mappedData.length === 0) return;

    const headers = Object.keys(mappedData[0]);
    const csvRows = [
      headers.join(","), // Header row
      ...mappedData.map((row) =>
        headers
          .map((key) => {
            const value = row[key];
            if (value instanceof Date) return `"${value.toISOString()}"`;
            if (typeof value === "string") return `"${value.replace(/"/g, '""')}"`; // Escape quotes
            return value;
          })
          .join(","),
      ),
    ];

    const csvContent = csvRows.join("\n");
    const blob = new Blob([csvContent], { type: "text/csv" });
    const url = URL.createObjectURL(blob);

    const link = document.createElement("a");
    link.href = url;
    link.download = `${csvFileName || "data"}_${filter.startDate.toISO()?.split("T")[0]}_${filter.endDate.toISO()?.split("T")[0]}.csv`;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    URL.revokeObjectURL(url);
  }, [mappedData, filter.startDate, filter.endDate, filter.granularity]);

  return (
    <Stack maxHeight={300}>
      <Table count={mappedData.length}>
        <TableHead
          tableRowProps={{
            sx: {
              position: "sticky",
              top: 0,
              bgcolor: "background.paper",
              backgroundImage: "var(--Paper-overlay)",
              boxShadow: "inset 0 -1px var(--mui-palette-divider)",
            },
          }}
        >
          {mappedData.length > 0 &&
            Object.keys(mappedData[0]).map((key) => {
              return <TableHeading key={key}>{formatKey(key)}</TableHeading>;
            })}
        </TableHead>
        <TableBody sx={{ overflowY: "auto" }}>
          {mappedData.map((item, i) => (
            <TableRow key={i}>
              {Object.entries(item).map(([key, value]) => (
                <TableCell
                  key={key}
                  sx={{ borderBottomWidth: i === mappedData.length - 1 ? 0 : 1 }}
                >
                  {value instanceof Date ? formatDate(value, filter.granularity) : value.toString()}
                </TableCell>
              ))}
            </TableRow>
          ))}
        </TableBody>
      </Table>
      <Stack
        alignItems="flex-end"
        pt={2}
        px={2}
        spacing={1}
        sx={{ borderTop: "1px solid", borderColor: "divider" }}
      >
        <Button color="inherit" variant="outlined" onClick={handleExportToCSV}>
          Export to CSV
        </Button>
      </Stack>
    </Stack>
  );
}

function formatKey(key: string) {
  return key
    .split("_")
    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
    .join(" ");
}

function getDateEntry(item: DataItem, dateKey?: string): [string, Date] | undefined {
  if (dateKey !== undefined && item.hasOwnProperty(dateKey)) {
    let date = item[dateKey];
    date = typeof date === "string" ? new Date(date) : date;
    if (!(date instanceof Date)) return undefined;

    return [dateKey, date];
  }

  const dateEntry = Object.entries(item).find(([_, value]) => isDateOrString(value)) as
    | [string, Date | string]
    | undefined;
  if (dateEntry === undefined) {
    return undefined;
  }

  const dateObj = dateEntry[1] instanceof Date ? dateEntry[1] : new Date(dateEntry[1]);
  return [dateEntry[0], dateObj];
}

function isDateOrString(value: unknown): value is Date | string {
  return (
    (value instanceof Date && !Number.isNaN(value.getTime())) ||
    (typeof value === "string" && !Number.isNaN(Date.parse(value)))
  );
}
