import { invoke } from "@tauri-apps/api/core";
import "./App.css";
import { useState, useEffect, useCallback } from "react";

// ─── Types ───────────────────────────────────────────────────────────────────

type StepStatus = "disabled" | "pending" | "running" | "completed" | "error" | "update_available";

interface StepDef {
  id: string;
  label: string;
  description: string;
  checkCmd: string;
  installCmd?: string;
  resultType: "string" | "boolean" | "driver_status";
  dependsOn: string[];
  invertBoolean?: boolean;
  infoOnly?: boolean;
  /** Only show this step on RHEL-family distros */
  rhelOnly?: boolean;
}

interface StepState {
  status: StepStatus;
  detail: string;
  error: string;
}

interface DriverStatus {
  installed: string;
  available: string;
  loaded: string;
  update_available: boolean;
}

interface DistroInfo {
  id: string;
  name: string;
  version: string;
  is_rhel_family: boolean;
}

// ─── Step Definitions ────────────────────────────────────────────────────────

const ALL_STEPS: StepDef[] = [
  {
    id: "detect_gpu",
    label: "Detect NVIDIA GPU",
    description: "Scan PCI devices for an NVIDIA graphics card",
    checkCmd: "check_nvidia_gpu",
    resultType: "string",
    dependsOn: [],
    infoOnly: true,
  },
  {
    id: "epel",
    label: "Enable EPEL Repository",
    description: "Extra Packages for Enterprise Linux — required before RPM Fusion on RHEL",
    checkCmd: "check_epel",
    installCmd: "install_epel",
    resultType: "boolean",
    dependsOn: ["detect_gpu"],
    rhelOnly: true,
  },
  {
    id: "rpmfusion_free",
    label: "Enable RPM Fusion Free Repository",
    description: "Required base repository for NVIDIA packages",
    checkCmd: "check_rpmfusion_free",
    installCmd: "install_rpmfusion_free",
    resultType: "boolean",
    dependsOn: ["detect_gpu"],
    // dependsOn adjusted dynamically for RHEL in getSteps()
  },
  {
    id: "rpmfusion_nonfree",
    label: "Enable RPM Fusion Non-Free Repository",
    description: "Contains proprietary NVIDIA driver packages",
    checkCmd: "check_rpmfusion_nonfree",
    installCmd: "install_rpmfusion_nonfree",
    resultType: "boolean",
    dependsOn: ["detect_gpu", "rpmfusion_free"],
  },
  {
    id: "system_update",
    label: "Running Kernel Is Up To Date",
    description: "Running kernel must match latest installed kernel for driver builds",
    checkCmd: "check_system_updated",
    installCmd: "update_system",
    resultType: "boolean",
    dependsOn: ["rpmfusion_free", "rpmfusion_nonfree"],
  },
  {
    id: "kernel_devel",
    label: "Kernel Development Headers Installed",
    description: "Required to compile NVIDIA kernel modules",
    checkCmd: "check_kernel_devel",
    installCmd: "install_kernel_devel",
    resultType: "boolean",
    dependsOn: ["system_update"],
  },
  {
    id: "secure_boot",
    label: "Secure Boot Status",
    description: "Detects if Secure Boot is enabled (info only)",
    checkCmd: "check_secure_boot",
    resultType: "boolean",
    dependsOn: ["detect_gpu"],
    infoOnly: true,
    invertBoolean: true,
  },
  {
    id: "secure_boot_tools",
    label: "Secure Boot Signing Tools",
    description: "Install kmodtool, akmods, mokutil, openssl for module signing",
    checkCmd: "check_secure_boot",
    installCmd: "install_secure_boot_tools",
    resultType: "boolean",
    dependsOn: ["kernel_devel"],
  },
  {
    id: "akmod_nvidia",
    label: "Install NVIDIA Driver (akmod-nvidia)",
    description: "Install akmod-nvidia + CUDA support packages",
    checkCmd: "check_akmod_nvidia",
    installCmd: "install_akmod_nvidia",
    resultType: "boolean",
    dependsOn: ["kernel_devel", "rpmfusion_nonfree"],
  },
  {
    id: "driver_status",
    label: "NVIDIA Driver Status",
    description: "Check driver version and available updates",
    checkCmd: "check_driver_status",
    installCmd: "update_nvidia_driver",
    resultType: "driver_status",
    dependsOn: ["akmod_nvidia"],
  },
];

/** Build the step list based on distro, adjusting deps for RHEL */
function getSteps(isRhel: boolean): StepDef[] {
  return ALL_STEPS
    .filter((s) => !s.rhelOnly || isRhel)
    .map((s) => {
      // On RHEL, rpmfusion_free depends on epel
      if (isRhel && s.id === "rpmfusion_free") {
        return { ...s, dependsOn: ["detect_gpu", "epel"] };
      }
      // On RHEL, rpmfusion_nonfree also depends on epel
      if (isRhel && s.id === "rpmfusion_nonfree") {
        return { ...s, dependsOn: ["detect_gpu", "rpmfusion_free", "epel"] };
      }
      return s;
    });
}

// ─── App Component ──────────────────────────────────────────────────────────

function App() {
  const [distro, setDistro] = useState<DistroInfo | null>(null);
  const [steps, setSteps] = useState<StepDef[]>([]);
  const [stepStates, setStepStates] = useState<Record<string, StepState>>({});
  const [secureBootEnabled, setSecureBootEnabled] = useState(false);
  const [errorModal, setErrorModal] = useState<{ stepId: string; error: string } | null>(null);
  const [refreshKey, setRefreshKey] = useState(0);
  const [isRefreshing, setIsRefreshing] = useState(false);

  // ── Detect distro on mount ──

  useEffect(() => {
    (async () => {
      try {
        const info = await invoke<DistroInfo>("detect_distro");
        setDistro(info);
        const resolvedSteps = getSteps(info.is_rhel_family);
        setSteps(resolvedSteps);

        const initial: Record<string, StepState> = {};
        for (const step of resolvedSteps) {
          initial[step.id] = { status: "disabled", detail: "", error: "" };
        }
        setStepStates(initial);
      } catch {
        // Fallback to Fedora if detection fails
        setDistro({ id: "fedora", name: "Unknown (Fedora assumed)", version: "0", is_rhel_family: false });
        const resolvedSteps = getSteps(false);
        setSteps(resolvedSteps);

        const initial: Record<string, StepState> = {};
        for (const step of resolvedSteps) {
          initial[step.id] = { status: "disabled", detail: "", error: "" };
        }
        setStepStates(initial);
      }
    })();
  }, []);

  // ── Helpers ──

  const updateStep = useCallback(
    (id: string, patch: Partial<StepState>) => {
      setStepStates((prev) => ({
        ...prev,
        [id]: { ...prev[id], ...patch },
      }));
    },
    []
  );

  const isStepUnlocked = useCallback(
    (step: StepDef, states: Record<string, StepState>): boolean => {
      return step.dependsOn.every((dep) => {
        const s = states[dep]?.status;
        return s === "completed" || s === "update_available";
      });
    },
    []
  );

  // ── Process a check result for a single step ──

  const processCheckResult = useCallback(
    (step: StepDef, result: string | boolean | DriverStatus): StepState => {
      if (step.resultType === "driver_status") {
        const ds = result as DriverStatus;

        if (!ds.installed) {
          return { status: "pending", detail: "Driver not installed", error: "" };
        }

        const loadedInfo = ds.loaded
          ? `Loaded: v${ds.loaded}`
          : "Module not loaded — reboot may be needed";

        if (ds.update_available) {
          return {
            status: "update_available",
            detail: `${loadedInfo} • Update available: ${ds.available}`,
            error: "",
          };
        }

        return {
          status: "completed",
          detail: `${loadedInfo} • Up to date (${ds.installed})`,
          error: "",
        };
      }

      if (step.resultType === "boolean") {
        if (step.id === "secure_boot") {
          return {
            status: "completed",
            detail: result ? "Secure Boot is enabled" : "Secure Boot is disabled",
            error: "",
          };
        }

        const val = step.invertBoolean ? !result : result;
        return {
          status: val ? "completed" : "pending",
          detail: val ? "Installed ✓" : step.installCmd ? "Action required" : "Not detected",
          error: "",
        };
      }

      // string
      return { status: "completed", detail: result as string, error: "" };
    },
    []
  );

  // ── Run a single check ──

  const runCheck = useCallback(
    async (step: StepDef) => {
      updateStep(step.id, { status: "running", detail: "Checking…", error: "" });

      try {
        const result = await invoke<string | boolean | DriverStatus>(step.checkCmd);

        if (step.id === "secure_boot") {
          setSecureBootEnabled(result as boolean);
        }

        const state = processCheckResult(step, result);
        updateStep(step.id, state);
      } catch (e: unknown) {
        const errorMsg = typeof e === "string" ? e : (e as Error).message || String(e);
        updateStep(step.id, { status: "error", detail: "Check failed", error: errorMsg });
      }
    },
    [updateStep, processCheckResult]
  );

  // ── Run all checks sequentially ──

  const runAllChecks = useCallback(async () => {
    if (steps.length === 0) return;

    setIsRefreshing(true);

    const fresh: Record<string, StepState> = {};
    for (const step of steps) {
      fresh[step.id] = { status: "disabled", detail: "", error: "" };
    }
    setStepStates(fresh);

    await new Promise((r) => setTimeout(r, 150));

    const states = { ...fresh };

    for (const step of steps) {
      // Skip secure_boot_tools if secure boot is disabled
      if (step.id === "secure_boot_tools" && !secureBootEnabled) {
        states[step.id] = { status: "completed", detail: "Skipped — Secure Boot disabled", error: "" };
        setStepStates((prev) => ({ ...prev, [step.id]: states[step.id] }));
        continue;
      }

      if (!isStepUnlocked(step, states)) {
        states[step.id] = { status: "disabled", detail: "", error: "" };
        setStepStates((prev) => ({ ...prev, [step.id]: states[step.id] }));
        continue;
      }

      setStepStates((prev) => ({
        ...prev,
        [step.id]: { status: "running", detail: "Checking…", error: "" },
      }));

      try {
        const result = await invoke<string | boolean | DriverStatus>(step.checkCmd);

        if (step.id === "secure_boot") {
          setSecureBootEnabled(result as boolean);
        }

        states[step.id] = processCheckResult(step, result);
      } catch (e: unknown) {
        const errorMsg = typeof e === "string" ? e : (e as Error).message || String(e);

        if (step.id === "detect_gpu") {
          states[step.id] = { status: "error", detail: "No NVIDIA GPU found", error: errorMsg };
          setStepStates((prev) => ({ ...prev, [step.id]: states[step.id] }));
          break;
        } else {
          states[step.id] = { status: "error", detail: "Check failed", error: errorMsg };
        }
      }

      setStepStates((prev) => ({ ...prev, [step.id]: states[step.id] }));
    }

    setIsRefreshing(false);
  }, [steps, isStepUnlocked, secureBootEnabled, processCheckResult]);

  // ── Run checks when steps are ready or refresh is requested ──

  useEffect(() => {
    if (steps.length > 0) {
      runAllChecks();
    }
  }, [steps, refreshKey]);

  // ── Install action ──

  const handleInstall = useCallback(
    async (step: StepDef) => {
      if (!step.installCmd) return;

      updateStep(step.id, { status: "running", detail: "Installing…", error: "" });

      try {
        const result = await invoke<string>(step.installCmd);
        updateStep(step.id, {
          status: "completed",
          detail: result || "Installed ✓",
        });

        setTimeout(() => {
          setRefreshKey((k) => k + 1);
        }, 500);
      } catch (e: unknown) {
        const errorMsg = typeof e === "string" ? e : (e as Error).message || String(e);
        updateStep(step.id, { status: "error", detail: "Install failed", error: errorMsg });
      }
    },
    [updateStep]
  );

  // ── Update driver action ──

  const handleUpdate = useCallback(
    async (step: StepDef) => {
      if (!step.installCmd) return;

      updateStep(step.id, { status: "running", detail: "Updating driver…", error: "" });

      try {
        await invoke<string>(step.installCmd);
        const result = await invoke<DriverStatus>(step.checkCmd);
        const state = processCheckResult(step, result);
        updateStep(step.id, state);
      } catch (e: unknown) {
        const errorMsg = typeof e === "string" ? e : (e as Error).message || String(e);
        updateStep(step.id, { status: "error", detail: "Update failed", error: errorMsg });
      }
    },
    [updateStep, processCheckResult]
  );

  // ── Computed values ──

  const completedCount = Object.values(stepStates).filter(
    (s) => s.status === "completed" || s.status === "update_available"
  ).length;
  const totalCount = steps.length;
  const progressPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;

  // ── Render helpers ──

  const renderStepIcon = (_step: StepDef, state: StepState, index: number) => {
    if (state.status === "completed") return "✓";
    if (state.status === "update_available") return "↑";
    if (state.status === "error") return "✕";
    if (state.status === "running") return <div className="spinner" />;
    return index + 1;
  };

  const renderStepAction = (step: StepDef, state: StepState) => {
    if (state.status === "running") {
      return <span className="status-badge checking">Running</span>;
    }
    if (state.status === "completed") {
      return <span className="status-badge done">Done</span>;
    }
    if (state.status === "update_available") {
      return (
        <button
          className="action-btn update"
          onClick={(e) => {
            e.stopPropagation();
            handleUpdate(step);
          }}
        >
          ⬆ Update
        </button>
      );
    }
    if (state.status === "error") {
      return (
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          <button
            className="action-btn retry"
            onClick={(e) => {
              e.stopPropagation();
              if (step.installCmd) {
                handleInstall(step);
              } else {
                runCheck(step);
              }
            }}
          >
            ↻ Retry
          </button>
          <span className="status-badge failed">Error</span>
        </div>
      );
    }
    if (state.status === "pending" && step.installCmd) {
      return (
        <button
          className="action-btn install"
          onClick={() => handleInstall(step)}
        >
          ⚡ Install
        </button>
      );
    }
    if (state.status === "pending" && !step.installCmd) {
      return <span className="status-badge checking">Info</span>;
    }
    return null;
  };

  // ── Distro badge ──

  const distroLabel = distro
    ? distro.is_rhel_family
      ? `${distro.name} (RHEL-family)`
      : distro.name
    : "Detecting…";

  return (
    <div className="app-container">
      {/* Header */}
      <header className="app-header">
        <div className="header-left">
          <div className="header-icon">⚡</div>
          <div>
            <div className="header-title">NVIDIA GPU Manager</div>
            <div className="header-subtitle">{distroLabel}</div>
          </div>
        </div>
        <div className="header-right">
          <button
            className={`refresh-btn ${isRefreshing ? "spinning" : ""}`}
            onClick={() => setRefreshKey((k) => k + 1)}
            disabled={isRefreshing || steps.length === 0}
          >
            <span className="icon">⟳</span>
            Re-scan
          </button>
        </div>
      </header>

      {/* Progress */}
      <section className="progress-section">
        <div className="progress-header">
          <span className="progress-label">Setup Progress</span>
          <span className="progress-count">
            <span className="done">{completedCount}</span> / {totalCount} steps
          </span>
        </div>
        <div className="progress-track">
          <div className="progress-fill" style={{ width: `${progressPercent}%` }} />
        </div>
      </section>

      {/* Steps */}
      <div className="steps-container">
        {steps.map((step, index) => {
          const state = stepStates[step.id];
          if (!state) return null;
          const cardClass = `step-card ${state.status === "update_available" ? "update-available" : state.status}`;

          return (
            <div
              key={step.id}
              className={cardClass}
              onClick={() => {
                if (state.status === "error" && state.error) {
                  setErrorModal({ stepId: step.id, error: state.error });
                }
              }}
              style={{ animationDelay: `${index * 50}ms` }}
            >
              <div className="step-index">{renderStepIcon(step, state, index)}</div>
              <div className="step-content">
                <div className="step-label">{step.label}</div>
                <div className="step-detail">
                  {state.detail || step.description}
                </div>
              </div>
              <div className="step-action">{renderStepAction(step, state)}</div>
            </div>
          );
        })}
      </div>

      {/* Footer */}
      <footer className="app-footer">
        <span className="footer-text">
          Built with <span className="heart">♥</span> for Fedora & RHEL • RPM Fusion powered
        </span>
      </footer>

      {/* Error Modal */}
      {errorModal && (
        <div className="modal-overlay" onClick={() => setErrorModal(null)}>
          <div className="modal-content" onClick={(e) => e.stopPropagation()}>
            <div className="modal-header">
              <div className="modal-header-left">
                <div className="modal-error-icon">⚠</div>
                <div>
                  <div className="modal-title">Error Log</div>
                  <div className="modal-step-name">
                    {steps.find((s) => s.id === errorModal.stepId)?.label}
                  </div>
                </div>
              </div>
              <button className="modal-close" onClick={() => setErrorModal(null)}>
                ✕
              </button>
            </div>
            <div className="modal-body">
              <pre className="error-log">{errorModal.error}</pre>
            </div>
            <div className="modal-footer">
              <button
                className="modal-btn secondary"
                onClick={() => setErrorModal(null)}
              >
                Dismiss
              </button>
              <button
                className="modal-btn primary"
                onClick={() => {
                  const step = steps.find((s) => s.id === errorModal.stepId);
                  if (step) {
                    setErrorModal(null);
                    if (step.installCmd) {
                      handleInstall(step);
                    } else {
                      runCheck(step);
                    }
                  }
                }}
              >
                ↻ Retry Step
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

export default App;
