"use client";

import type { AuditActorType } from "@calcom/features/booking-audit/lib/repository/IAuditActorRepository";
import ServerTrans from "@calcom/lib/components/ServerTrans";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Avatar } from "@calcom/ui/components/avatar";
import { Button } from "@calcom/ui/components/button";
import { FilterSearchField, Select } from "@calcom/ui/components/form";
import { Icon, type IconName } from "@calcom/ui/components/icon";
import { SkeletonText } from "@calcom/ui/components/skeleton";
import { Tooltip } from "@calcom/ui/components/tooltip";
import { format, formatDistanceToNow } from "date-fns";
import Link from "next/link";
import { useState } from "react";

interface BookingHistoryProps {
  bookingUid: string;
}

type TranslationComponent = {
  type: "link";
  href: string;
};

type TranslationWithParams = {
  key: string;
  params?: Record<string, string | number>;
  components?: TranslationComponent[];
};

type DisplayFieldValue =
    | { type: "translationKey"; valueKey: string }
    | { type: "rawValue"; value: string }
    | { type: "rawValues"; values: string[] }
    | { type: "translationsWithParams"; valuesWithParams: TranslationWithParams[] };

type DisplayField = {
    labelKey: string;
    fieldValue: DisplayFieldValue;
};

type AuditLog = {
  id: string;
  action: string;
  type: string;
  timestamp: string;
  source: string;
  displayJson?: Record<string, unknown> | null;
  actionDisplayTitle: TranslationWithParams;
  displayFields?: DisplayField[] | null;
  actor: {
    type: AuditActorType;
    displayName: string | null;
    displayEmail: string | null;
    displayAvatar: string | null;
  };
  impersonatedBy?: {
    displayName: string;
    displayEmail: string | null;
    displayAvatar: string | null;
  } | null;
  hasError?: boolean;
};

interface BookingLogsFiltersProps {
  searchTerm: string;
  onSearchChange: (value: string) => void;
  actorFilter: string | null;
  onActorFilterChange: (value: string | null) => void;
  actorOptions: Array<{ label: string; value: string }>;
}

interface BookingLogsTimelineProps {
  logs: AuditLog[];
}

const ACTION_ICON_MAP: Record<string, IconName> = {
  CREATED: "calendar",
  CANCELLED: "ban",
  REJECTED: "ban",
  ACCEPTED: "check",
  RESCHEDULED: "pencil",
  RESCHEDULE_REQUESTED: "pencil",
  REASSIGNMENT: "user-check",
  ATTENDEE_ADDED: "user-check",
  ATTENDEE_REMOVED: "user-check",
  LOCATION_CHANGED: "map-pin",
  NO_SHOW_UPDATED: "ban",
} as const;

const ACTOR_ROLE_LABEL_MAP: Record<AuditActorType, string | null> = {
  GUEST: "guest",
  ATTENDEE: "attendee",
  SYSTEM: null,
  USER: null,
  APP: null,
} as const;

function BookingLogsFilters({
  searchTerm,
  onSearchChange,
  actorFilter,
  onActorFilterChange,
  actorOptions,
}: BookingLogsFiltersProps) {
  const { t } = useLocale();

  return (
    <div className="flex flex-wrap gap-2 items-start">
      <div className="flex-1 min-w-[200px]">
        <FilterSearchField
          size="sm"
          value={searchTerm}
          onChange={(e) => onSearchChange(e.target.value)}
          containerClassName=""
        />
      </div>

      <div className="min-w-[140px]">
        <Select
          size="sm"
          value={
            actorFilter
              ? { label: `${t("actor")}: ${actorFilter}`, value: actorFilter }
              : { label: `${t("actor")}: ${t("all")}`, value: "" }
          }
          onChange={(option) => {
            if (!option) return;
            onActorFilterChange(option.value || null);
          }}
          options={[{ label: t("all"), value: "" }, ...actorOptions]}
        />
      </div>
    </div>
  );
}

/**
 * Renders the action display title with support for Trans component interpolation
 * Handles translations with embedded components (e.g., links) for proper i18n support
 */
function ActionTitle({ actionDisplayTitle }: { actionDisplayTitle: TranslationWithParams }) {
  const { t } = useLocale();

  if (actionDisplayTitle.components?.length) {
    return (
      <ServerTrans
        t={t}
        i18nKey={actionDisplayTitle.key}
        values={actionDisplayTitle.params}
        components={actionDisplayTitle.components.map((comp) =>
          comp.type === "link" ? (
            <Link
              key={comp.href}
              href={comp.href}
              target="_blank"
              rel="noopener noreferrer"
              className="text-emphasis underline hover:no-underline"
            />
          ) : (
            <span key={comp.href} />
          )
        )}
      />
    );
  }

  return <>{t(actionDisplayTitle.key, actionDisplayTitle.params)}</>;
}

interface JsonViewerProps {
  data: Record<string, unknown> | null;
}

function JsonViewer({ data }: JsonViewerProps) {
  if (!data || Object.keys(data).length === 0) {
    return null;
  }

  const jsonString = JSON.stringify(data, null, 2);
  const lines = jsonString.split("\n");
  const lineCount = lines.length;
  const lineNumberWidth = Math.max(2, Math.ceil(Math.log10(lineCount)) + 1);

  return (
    <div className="bg-default p-3 rounded-md text-[10px] overflow-x-auto font-mono">
      {lines.map((line, idx) => (
        <div key={idx} className="flex gap-2">
          <span
            className="text-subtle select-none text-right shrink-0"
            style={{ minWidth: `${lineNumberWidth}ch` }}>
            {idx + 1}
          </span>
          <span className="text-default whitespace-pre-wrap break-all">{line || " "}</span>
        </div>
      ))}
    </div>
  );
}

interface DisplayFieldValueComponentProps {
  fieldValue: DisplayFieldValue;
}

function DisplayFieldValueComponent({ fieldValue }: DisplayFieldValueComponentProps) {
  const { t } = useLocale();

  switch (fieldValue.type) {
    case "translationsWithParams":
      return (
        <span className="flex flex-col">
          {fieldValue.valuesWithParams.map((v, i) => (
            <span className="p-0.5" key={i}>
              {t(v.key, v.params)}
            </span>
          ))}
        </span>
      );
    case "rawValues":
      return (
        <span className="flex flex-col">
          {fieldValue.values.map((v, i) => (
            <span className="p-0.5" key={i}>
              {v}
            </span>
          ))}
        </span>
      );
    case "rawValue":
      return <>{fieldValue.value}</>;
    case "translationKey":
      return <>{t(fieldValue.valueKey)}</>;
  }
}

function BookingLogsTimeline({ logs }: BookingLogsTimelineProps) {
  const { t } = useLocale();
  const [expandedLogIds, setExpandedLogIds] = useState<Set<string>>(new Set());
  const [showJsonMap, setShowJsonMap] = useState<Record<string, boolean>>({});

  const toggleExpand = (logId: string) => {
    setExpandedLogIds((prev) => {
      const newSet = new Set(prev);
      if (newSet.has(logId)) {
        newSet.delete(logId);
      } else {
        newSet.add(logId);
      }
      return newSet;
    });
  };

  const toggleJson = (logId: string) => {
    setShowJsonMap((prev) => ({
      ...prev,
      [logId]: !prev[logId],
    }));
  };

  if (logs.length === 0) {
    return (
      <div className="text-center py-12">
        <p className="text-muted">{t("no_audit_logs_found")}</p>
      </div>
    );
  }

  return (
    <div className="flex flex-col gap-0.5">
      {logs.map((log, index) => {
        const isLast = index === logs.length - 1;
        const isExpanded = expandedLogIds.has(log.id);
        const showJson = showJsonMap[log.id] || false;
        const actorRole = ACTOR_ROLE_LABEL_MAP[log.actor.type] ?? null;
        return (
          <div key={log.id} className="flex gap-1">
            <div className="flex flex-col items-center self-stretch">
              <div className="pt-2 shrink-0">
                <div className="bg-subtle rounded-[3.556px] p-1 flex items-center justify-center w-4 h-4">
                  <Icon
                    name={log.hasError ? "triangle-alert" : (ACTION_ICON_MAP[log.action] ?? "sparkles")}
                    className={`h-3 w-3 ${log.hasError ? "text-attention" : "text-subtle"}`}
                  />
                </div>
              </div>
              {!isLast && <div className="w-px bg-subtle flex-1 min-h-0" />}
            </div>

            <div className="flex-1 rounded-lg py-2">
              <div className="px-3 mb-2">
                <div className="flex items-start justify-between gap-3">
                  <div className="flex-1 min-w-0">
                    <h3 className="text-sm font-medium leading-4 text-emphasis">
                      <ActionTitle actionDisplayTitle={log.actionDisplayTitle} />
                    </h3>
                    <div className="flex items-center gap-1 mt-1 text-xs text-subtle">
                      {log.actor.displayAvatar && (
                        <Avatar
                          size="xs"
                          imageSrc={log.actor.displayAvatar}
                          alt={log.actor.displayName || ""}
                        />
                      )}
                      <span>
                        {log.actor.displayName}
                        {actorRole && <span>{` (${t(actorRole)})`}</span>}
                      </span>
                      <span>•</span>
                      <Tooltip content={format(new Date(log.timestamp), "yyyy-MM-dd HH:mm:ss")}>
                        <span>{formatDistanceToNow(new Date(log.timestamp), { addSuffix: true })}</span>
                      </Tooltip>
                    </div>
                  </div>
                </div>
              </div>

              <div className="px-3">
                <div className="bg-muted rounded-lg border border-muted">
                  <div className="py-1">
                    <Button
                      color="minimal"
                      size="sm"
                      onClick={() => toggleExpand(log.id)}
                      StartIcon={isExpanded ? "chevron-down" : "chevron-right"}
                      className="justify-start text-xs font-medium text-subtle h-6">
                      {isExpanded ? t("hide_details") : t("show_details")}
                    </Button>
                  </div>

                  {isExpanded && (
                    <div className="bg-default rounded-lg m-0.5 text-xs">
                      {/* Render displayFields if available, otherwise show type */}
                      {log.displayFields && log.displayFields.length > 0
                        ? log.displayFields.map((field, idx) => (
                          <div
                            key={idx}
                            className="flex items-start gap-2 py-2 border-b px-3 border-subtle">
                            <span className="font-medium text-emphasis w-[140px]">{t(field.labelKey)}</span>
                            <span className="font-medium">
                              <DisplayFieldValueComponent fieldValue={field.fieldValue} />
                            </span>
                          </div>
                        ))
                        : null}
                      <div className="flex items-start gap-2 py-2 border-b px-3 border-subtle">
                        <span className="font-medium text-emphasis w-[140px]">{t("actor")}</span>
                        <span className="text-default">{log.actor.displayName || log.actor.type}</span>
                      </div>
                      {log.impersonatedBy && (
                        <div className="flex items-start gap-2 py-2 border-b px-3 border-subtle">
                          <span className="font-medium text-emphasis w-[140px]">
                            {t("booking_audit_action.actor_impersonated_by", {
                              actor: log.actor.displayName,
                            })}
                          </span>
                          <span className="text-default">{log.impersonatedBy.displayName}</span>
                        </div>
                      )}
                      <div className="flex items-start gap-2 py-2 border-b px-3 border-subtle">
                        <span className="font-medium text-emphasis w-[140px]">
                          {t("booking_audit_action.source")}
                        </span>
                        <span className="text-default">{log.source}</span>
                      </div>
                      <div className="flex items-start gap-2 py-2 px-3 border-b border-subtle">
                        <span className="font-medium text-emphasis w-[140px]">{t("timestamp")}</span>
                        <span className="text-default">
                          {format(new Date(log.timestamp), "yyyy-MM-dd HH:mm:ss")}
                        </span>
                      </div>
                      {log.displayJson && Object.keys(log.displayJson).length > 0 && (
                        <div>
                          <div className="flex flex-col items-start gap-2 py-1 px-3 border-b border-subtle">
                            <Button
                              color="minimal"
                              size="sm"
                              onClick={() => toggleJson(log.id)}
                              StartIcon={showJson ? "chevron-down" : "chevron-right"}
                              className="-ml-3 h-6 px-2 font-medium text-xs">
                              {t("json")}
                            </Button>
                          </div>
                          <div>{showJson && <JsonViewer data={log.displayJson} />}</div>
                        </div>
                      )}
                    </div>
                  )}
                </div>
              </div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

function useBookingLogsFilters(auditLogs: AuditLog[], searchTerm: string, actorFilter: string | null) {
  const { t } = useLocale();

  const filteredLogs = auditLogs.filter((log) => {
    const doesMatchDisplayFields = (): boolean => {
      return (
        log.displayFields?.some((field) => {
          const searchLower = searchTerm.toLowerCase();
          const translatedLabel = field.labelKey ? t(field.labelKey) : "";
          if (translatedLabel.toLowerCase().includes(searchLower)) {
            return true;
          }
          const { fieldValue } = field;
          switch (fieldValue.type) {
            case "translationKey":
              return t(fieldValue.valueKey).toLowerCase().includes(searchLower);
            case "rawValue":
              return fieldValue.value.toLowerCase().includes(searchLower);
            case "rawValues":
              return fieldValue.values.some((v) => v.toLowerCase().includes(searchLower));
            case "translationsWithParams":
              return fieldValue.valuesWithParams.some(
                (v) =>
                  t(v.key, v.params).toLowerCase().includes(searchLower) ||
                  Object.values(v.params ?? {}).some((param) =>
                    param?.toString().toLowerCase().includes(searchLower)
                  )
              );
          }
        }) ?? false
      );
    };

    const doesMatchActionDisplayTitle = (): boolean => {
      const translatedTitle = log.actionDisplayTitle.key
        ? t(log.actionDisplayTitle.key, log.actionDisplayTitle.params ?? {})
        : "";

      return translatedTitle.toLowerCase().includes(searchTerm.toLowerCase());
    };

    const matchesSearch =
      !searchTerm ||
      log.action.toLowerCase().includes(searchTerm.toLowerCase()) ||
      log.actor.displayName?.toLowerCase().includes(searchTerm.toLowerCase()) ||
      doesMatchDisplayFields() ||
      doesMatchActionDisplayTitle();

    const matchesActor = !actorFilter || log.actor.displayName === actorFilter;

    return matchesSearch && matchesActor;
  });

  const uniqueActorNames = Array.from(
    new Set(auditLogs.map((log) => log.actor.displayName).filter((name): name is string => Boolean(name)))
  );

  const actorOptions = uniqueActorNames.map((actorName) => ({
    label: actorName,
    value: actorName,
  }));

  return { filteredLogs, actorOptions };
}

export function BookingHistory({ bookingUid }: BookingHistoryProps) {
  const [searchTerm, setSearchTerm] = useState("");
  const [actorFilter, setActorFilter] = useState<string | null>(null);
  const { t } = useLocale();
  const { data, isLoading, error } = trpc.viewer.bookings.getBookingHistory.useQuery({
    bookingUid,
  });

  const auditLogs = data?.auditLogs || [];

  const { filteredLogs, actorOptions } = useBookingLogsFilters(auditLogs, searchTerm, actorFilter);

  if (error) {
    return (
      <div className="flex items-center justify-center py-12">
        <div className="text-center">
          <p className="text-red-600 font-medium">{t("error_loading_booking_logs")}</p>
          <p className="text-sm text-gray-500 mt-2">{error.message}</p>
        </div>
      </div>
    );
  }

  if (isLoading) {
    return (
      <div className="space-y-4">
        <SkeletonText className="h-12 w-full" />
        <SkeletonText className="h-24 w-full" />
        <SkeletonText className="h-24 w-full" />
        <SkeletonText className="h-24 w-full" />
      </div>
    );
  }

  return (
    <div className="space-y-6">
      <BookingLogsFilters
        searchTerm={searchTerm}
        onSearchChange={setSearchTerm}
        actorFilter={actorFilter}
        onActorFilterChange={setActorFilter}
        actorOptions={actorOptions}
      />

      <BookingLogsTimeline logs={filteredLogs} />
    </div>
  );
}
