import { useCallback, useEffect, useMemo, useState } from "react";
import {
  Alert,
  Box,
  Button,
  Card,
  Chip,
  FormControl,
  FormHelperText,
  FormLabel,
  Input,
  Link,
  Option,
  Select,
  Stack,
  Textarea,
  Typography,
} from "@mui/joy";
import { Navigate, useLocation, useNavigate, useParams } from "react-router";
import { ArrowLeft, RefreshCw, Save } from "lucide-react";
import Editor, { useMonaco } from "@monaco-editor/react";
import type { FunctionDto, ProjectDto, UpdateFunctionDto } from "@sigma/common";
import { apiClient } from "../../../lib/api";
import { useAuth } from "../../../context/auth-context";
import { configureEditor } from "../../../lib/editor";
import { BACKEND_BASE } from "../../../config";

const PATH_REGEX =
  /^\/(?:$|(?:[A-Za-z0-9._~-]+|:[A-Za-z0-9_]+)(?:\/(?:[A-Za-z0-9._~-]+|:[A-Za-z0-9_]+))*)$/;

const HTTP_METHODS = [
  "GET",
  "POST",
  "PUT",
  "PATCH",
  "DELETE",
  "OPTIONS",
  "HEAD",
];

function sanitizeMethods(values?: string[] | null): string[] {
  if (!values?.length) {
    return [];
  }

  return Array.from(new Set(values.map((method) => method.toUpperCase())));
}

function normalizePath(value: string): string {
  if (!value.startsWith("/")) {
    return `/${value}`;
  }

  if (value.length > 1 && value.endsWith("/")) {
    return value.slice(0, -1);
  }

  return value;
}

type UpdateFunctionPayload = UpdateFunctionDto & {
  methods?: string[];
};

export function FunctionDetailPage() {
  const monaco = useMonaco();

  const navigate = useNavigate();
  const location = useLocation();
  const { projectId, functionId } = useParams<{
    projectId: string;
    functionId: string;
  }>();
  const { isAuthenticated, loading: authLoading } = useAuth();

  const [project, setProject] = useState<ProjectDto | null>(null);
  const [func, setFunc] = useState<FunctionDto | null>(null);
  const [name, setName] = useState("");
  const [path, setPath] = useState("/");
  const [methods, setMethods] = useState<string[]>([]);
  const [code, setCode] = useState<string>(
    "// Write your function code here\n"
  );
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [success, setSuccess] = useState<string | null>(null);

  const normalizedPath = useMemo(
    () => normalizePath(path.trim() || "/"),
    [path]
  );
  const pathIsValid = PATH_REGEX.test(normalizedPath);
  const canSave = Boolean(name.trim()) && pathIsValid && !saving;
  const functionUrl = useMemo(() => {
    if (!project) {
      return null;
    }

    const trimmedBase = BACKEND_BASE.replace(/\/+$/, "");
    const execBase = `${trimmedBase ? trimmedBase : ""}/api/v1/exec/${project.slug}`;
    const invocationPath = normalizedPath === "/" ? "" : normalizedPath;

    return `${execBase}${invocationPath}`;
  }, [project, normalizedPath]);

  const logs = useMemo(() => func?.logs ?? [], [func]);
  const hasLogs = logs.length > 0;
  const logsCountLabel = hasLogs
    ? `${logs.length} entr${logs.length === 1 ? "y" : "ies"}`
    : "No logs yet";
  const logsValue = useMemo(
    () => (hasLogs ? [...logs].reverse().join("\n") : ""),
    [hasLogs, logs]
  );

  useEffect(() => {
    if (monaco) {
      configureEditor(monaco);
    }
  }, [monaco]);

  const handleBack = () => {
    if (window.history.length > 2) {
      navigate(-1);
    } else if (projectId) {
      navigate(`/projects/${projectId}`);
    } else {
      navigate("/");
    }
  };

  const loadFunction = useCallback(async () => {
    if (!projectId || !functionId) {
      setError("Missing identifiers for project or function");
      setLoading(false);
      return;
    }

    setLoading(true);
    setError(null);

    try {
      const { data } = await apiClient.get<ProjectDto>(
        `/projects/${projectId}`
      );
      const matchingFunction = data.functions.find(
        (item) => item._id === functionId
      );

      if (!matchingFunction) {
        setError("We couldn't find that function in this project.");
        setProject(data);
        setFunc(null);
        return;
      }

      setProject(data);
      setFunc(matchingFunction);
      setName(matchingFunction.name ?? "");
      setPath(matchingFunction.path ?? "/");
      setMethods(sanitizeMethods(matchingFunction.methods));
      setCode(matchingFunction.code ?? "");
      setSuccess(null);
    } catch (err) {
      const message =
        err instanceof Error
          ? err.message
          : "Unable to load function details. Please try again.";
      setError(message);
      setFunc(null);
      setProject(null);
    } finally {
      setLoading(false);
    }
  }, [projectId, functionId]);

  useEffect(() => {
    if (!isAuthenticated) {
      return;
    }

    loadFunction();
  }, [isAuthenticated, loadFunction]);

  if (!authLoading && !isAuthenticated) {
    return (
      <Navigate
        to="/login"
        replace
        state={{ from: location.pathname ?? "/" }}
      />
    );
  }

  if (!projectId || !functionId) {
    return (
      <Box sx={{ p: 3 }}>
        <Alert color="danger" variant="soft">
          Missing project or function identifier. Please return to the dashboard
          and try again.
        </Alert>
      </Box>
    );
  }

  const handleMethodsChange = (_: unknown, newValue: string[] | null) => {
    setMethods(newValue?.map((method) => method.toUpperCase()) ?? []);
  };

  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    if (!canSave || !projectId || !functionId) {
      return;
    }

    setSaving(true);
    setSuccess(null);
    setError(null);

    const payload: UpdateFunctionPayload = {
      name: name.trim(),
      path: normalizedPath,
      code: code ?? "",
      methods: methods.length ? methods : undefined,
    };

    try {
      const { data } = await apiClient.put<FunctionDto>(
        `/projects/${projectId}/functions/${functionId}`,
        payload
      );

      setFunc(data);
      setName(data.name);
      setPath(data.path);
      setMethods(sanitizeMethods(data.methods));
      setCode(data.code ?? "");
      setSuccess("Function updated successfully.");
    } catch (err) {
      const message =
        err instanceof Error ? err.message : "Failed to save function.";
      setError(message);
    } finally {
      setSaving(false);
    }
  };

  return (
    <Box sx={{ p: 3, display: "flex", justifyContent: "center" }}>
      <Box
        sx={{
          width: "100%",
          maxWidth: 960,
          display: "flex",
          flexDirection: "column",
          gap: 2,
        }}
      >
        <Button
          variant="plain"
          color="neutral"
          size="sm"
          startDecorator={<ArrowLeft size={16} />}
          onClick={handleBack}
          sx={{ alignSelf: "flex-start" }}
        >
          Back to project
        </Button>

        <Stack
          direction="row"
          justifyContent="space-between"
          alignItems={{ xs: "flex-start", md: "center" }}
          flexWrap="wrap"
          gap={1.5}
        >
          <Box>
            <Typography level="h2" fontWeight={700}>
              {func?.name ?? "Function"}
            </Typography>
            <Typography level="body-sm" textColor="neutral.500">
              {project
                ? `${project.name} · ${func?.path ?? ""}`
                : "Loading project..."}
            </Typography>
          </Box>
          <Button
            variant="outlined"
            color="neutral"
            size="sm"
            startDecorator={<RefreshCw size={16} />}
            onClick={loadFunction}
            disabled={loading}
          >
            Refresh
          </Button>
        </Stack>

        {error ? (
          <Alert color="danger" variant="soft">
            {error}
          </Alert>
        ) : null}

        {success ? (
          <Alert color="success" variant="soft">
            {success}
          </Alert>
        ) : null}

        <Card sx={{ p: 3 }}>
          {loading ? (
            <Typography level="body-sm" textColor="neutral.500">
              Loading function details...
            </Typography>
          ) : func ? (
            <Stack component="form" gap={2.5} onSubmit={handleSubmit}>
              <FormControl required>
                <FormLabel>Name</FormLabel>
                <Input
                  placeholder="Webhook handler"
                  value={name}
                  onChange={(event) => setName(event.target.value)}
                />
              </FormControl>

              <FormControl required error={!pathIsValid}>
                <FormLabel>Path</FormLabel>
                <Input
                  placeholder="/webhooks/payments/:id"
                  value={path}
                  onChange={(event) => setPath(event.target.value)}
                />
                <FormHelperText>
                  Must start with /. Use :param for dynamic segments.
                </FormHelperText>
                {project && pathIsValid && functionUrl ? (
                  <FormHelperText>
                    Available at&nbsp;
                    <Link
                      href={functionUrl}
                      target="_blank"
                      rel="noopener noreferrer"
                    >
                      {functionUrl}
                    </Link>
                  </FormHelperText>
                ) : null}
              </FormControl>

              <FormControl>
                <FormLabel>Allowed methods</FormLabel>
                <Select
                  multiple
                  placeholder="Any method"
                  value={methods}
                  onChange={handleMethodsChange}
                >
                  {HTTP_METHODS.map((method) => (
                    <Option key={method} value={method}>
                      {method}
                    </Option>
                  ))}
                </Select>
                {methods.length ? (
                  <Stack direction="row" gap={0.5} flexWrap="wrap" mt={1}>
                    {methods.map((method) => (
                      <Chip key={method} size="sm" variant="soft">
                        {method}
                      </Chip>
                    ))}
                  </Stack>
                ) : null}
                <FormHelperText>
                  Leave empty to accept any HTTP method.
                </FormHelperText>
              </FormControl>

              <FormControl>
                <FormLabel>Code</FormLabel>
                <Box
                  sx={{
                    borderRadius: "md",
                    border: "1px solid",
                    borderColor: "divider",
                  }}
                >
                  <Editor
                    height="420px"
                    defaultLanguage="javascript"
                    theme="vs-light"
                    value={code}
                    onChange={(value: string | undefined) =>
                      setCode(value ?? "")
                    }
                    options={{
                      minimap: { enabled: false },
                      tabSize: 2,
                      fontSize: 14,
                      automaticLayout: true,
                      fixedOverflowWidgets: true,
                    }}
                  />
                </Box>
                <FormHelperText>
                  <Typography fontSize="inherit">
                    Write your function code in plain JavaScript.
                    <br />
                    Use{" "}
                    <Typography variant="soft" fontSize="inherit">
                      req
                    </Typography>{" "}
                    and{" "}
                    <Typography variant="soft" fontSize="inherit">
                      res
                    </Typography>{" "}
                    objects to handle requests and responses.
                    <br />
                    You can use{" "}
                    <Typography variant="soft" fontSize="inherit">
                      {`scope_${project?.slug}`}
                    </Typography>{" "}
                    table within your SQL queries. Just be sure to create it
                    first.
                  </Typography>
                </FormHelperText>
              </FormControl>

              <FormControl>
                <Stack
                  direction="row"
                  justifyContent="space-between"
                  alignItems="center"
                  mb={0.5}
                >
                  <FormLabel sx={{ mb: 0 }}>Recent logs</FormLabel>
                  <Chip size="sm" variant="soft" color="neutral">
                    {logsCountLabel}
                  </Chip>
                </Stack>
                <Textarea
                  minRows={6}
                  maxRows={12}
                  variant="outlined"
                  color="neutral"
                  value={logsValue}
                  readOnly
                  placeholder="Logs will appear here after the function runs."
                  sx={{
                    fontFamily:
                      "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace",
                    bgcolor: "background.level1",
                  }}
                />
                <FormHelperText>
                  {hasLogs
                    ? "Newest entries are shown first. Use Refresh to pull the latest logs."
                    : "Invoke this function to generate logs, then use Refresh to load them."}
                </FormHelperText>
              </FormControl>

              <Stack direction="row" gap={1.5} justifyContent="flex-end">
                <Button
                  type="button"
                  variant="outlined"
                  color="neutral"
                  onClick={handleBack}
                  disabled={saving}
                >
                  Cancel
                </Button>
                <Button
                  type="submit"
                  startDecorator={<Save size={18} />}
                  loading={saving}
                  disabled={!canSave}
                >
                  Save changes
                </Button>
              </Stack>
            </Stack>
          ) : (
            <Typography level="body-sm" textColor="neutral.500">
              Select a function from the project to begin editing.
            </Typography>
          )}
        </Card>
      </Box>
    </Box>
  );
}
