import {
  redirect,
  type ActionFunctionArgs,
  type LoaderFunctionArgs,
} from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { QueryEditor } from "~/components/query/QueryEditor";
import { env } from "~/env.server";
import { findProjectBySlug } from "~/models/project.server";
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
import { QueryPresenter } from "~/presenters/v3/QueryPresenter.server";
import { executeQuery, getDefaultPeriod } from "~/services/queryService.server";
import { requireUser } from "~/services/session.server";
import { EnvironmentParamSchema, queryPath } from "~/utils/pathBuilder";
import { canAccessQuery } from "~/v3/canAccessQuery.server";
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { useEnvironment } from "~/hooks/useEnvironment";

/** Convert a Date or ISO string to ISO string format */
function toISOString(value: Date | string): string {
  if (typeof value === "string") {
    return value;
  }
  return value.toISOString();
}

export const loader = async ({ request, params }: LoaderFunctionArgs) => {
  const user = await requireUser(request);
  const { projectParam, organizationSlug, envParam } = EnvironmentParamSchema.parse(params);

  const canAccess = await canAccessQuery({
    userId: user.id,
    isAdmin: user.admin,
    isImpersonating: user.isImpersonating,
    organizationSlug,
  });
  if (!canAccess) {
    throw redirect("/");
  }

  const project = await findProjectBySlug(organizationSlug, projectParam, user.id);
  if (!project) {
    throw new Response(undefined, {
      status: 404,
      statusText: "Project not found",
    });
  }

  const environment = await findEnvironmentBySlug(project.id, envParam, user.id);
  if (!environment) {
    throw new Response(undefined, {
      status: 404,
      statusText: "Environment not found",
    });
  }

  const presenter = new QueryPresenter();
  const { defaultQuery, history } = await presenter.call({
    organizationId: project.organizationId,
  });

  // Admins and impersonating users can use EXPLAIN
  const isAdmin = user.admin || user.isImpersonating;

  return typedjson({
    defaultQuery,
    defaultPeriod: await getDefaultPeriod(project.organizationId),
    history,
    isAdmin,
    maxRows: env.QUERY_CLICKHOUSE_MAX_RETURNED_ROWS,
  });
};

const ActionSchema = z.object({
  query: z.string().min(1, "Query is required"),
  scope: z.enum(["environment", "project", "organization"]),
  explain: z.enum(["true", "false"]).nullable().optional(),
  period: z.string().nullable().optional(),
  from: z.string().nullable().optional(),
  to: z.string().nullable().optional(),
});

export const action = async ({ request, params }: ActionFunctionArgs) => {
  const user = await requireUser(request);
  const { projectParam, organizationSlug, envParam } = EnvironmentParamSchema.parse(params);

  const canAccess = await canAccessQuery({
    userId: user.id,
    isAdmin: user.admin,
    isImpersonating: user.isImpersonating,
    organizationSlug,
  });

  if (!canAccess) {
    return typedjson(
      {
        error: "Unauthorized",
        rows: null,
        columns: null,
        stats: null,
        hiddenColumns: null,
        reachedMaxRows: null,
        explainOutput: null,
        generatedSql: null,
        periodClipped: null,
      },
      { status: 403 }
    );
  }

  const project = await findProjectBySlug(organizationSlug, projectParam, user.id);
  if (!project) {
    return typedjson(
      {
        error: "Project not found",
        rows: null,
        columns: null,
        stats: null,
        hiddenColumns: null,
        reachedMaxRows: null,
        explainOutput: null,
        generatedSql: null,
        periodClipped: null,
      },
      { status: 404 }
    );
  }

  const environment = await findEnvironmentBySlug(project.id, envParam, user.id);
  if (!environment) {
    return typedjson(
      {
        error: "Environment not found",
        rows: null,
        columns: null,
        stats: null,
        hiddenColumns: null,
        reachedMaxRows: null,
        explainOutput: null,
        generatedSql: null,
        periodClipped: null,
      },
      { status: 404 }
    );
  }

  const formData = await request.formData();
  const parsed = ActionSchema.safeParse({
    query: formData.get("query"),
    scope: formData.get("scope"),
    explain: formData.get("explain"),
    period: formData.get("period"),
    from: formData.get("from"),
    to: formData.get("to"),
  });

  if (!parsed.success) {
    return typedjson(
      {
        error: parsed.error.errors.map((e) => e.message).join(", "),
        rows: null,
        columns: null,
        stats: null,
        hiddenColumns: null,
        reachedMaxRows: null,
        explainOutput: null,
        generatedSql: null,
        periodClipped: null,
      },
      { status: 400 }
    );
  }

  const { query, scope, explain: explainParam, period, from, to } = parsed.data;
  // Only allow explain for admins/impersonating users
  const isAdmin = user.admin || user.isImpersonating;
  const explain = explainParam === "true" && isAdmin;

  try {
    const queryResult = await executeQuery({
      name: "query-page",
      query,
      scope,
      organizationId: project.organizationId,
      projectId: project.id,
      environmentId: environment.id,
      explain,
      period,
      from,
      to,
      history: {
        source: "DASHBOARD",
        userId: user.id,
        skip: user.isImpersonating,
      },
    });

    if (!queryResult.success) {
      return typedjson(
        {
          error: queryResult.error.message,
          rows: null,
          columns: null,
          stats: null,
          hiddenColumns: null,
          reachedMaxRows: null,
          explainOutput: null,
          generatedSql: null,
          queryId: null,
          periodClipped: null,
        },
        { status: 400 }
      );
    }

    return typedjson({
      error: null,
      rows: queryResult.result.rows,
      columns: queryResult.result.columns,
      stats: queryResult.result.stats,
      hiddenColumns: queryResult.result.hiddenColumns ?? null,
      reachedMaxRows: queryResult.result.reachedMaxRows,
      explainOutput: queryResult.result.explainOutput ?? null,
      generatedSql: queryResult.result.generatedSql ?? null,
      queryId: queryResult.queryId,
      periodClipped: queryResult.periodClipped,
      maxQueryPeriod: queryResult.maxQueryPeriod,
    });
  } catch (err) {
    const errorMessage = err instanceof Error ? err.message : "Unknown error executing query";
    return typedjson(
      {
        error: errorMessage,
        rows: null,
        columns: null,
        stats: null,
        hiddenColumns: null,
        reachedMaxRows: null,
        explainOutput: null,
        generatedSql: null,
        queryId: null,
        periodClipped: null,
      },
      { status: 500 }
    );
  }
};

export default function Page() {
  const { defaultPeriod, defaultQuery, history, isAdmin, maxRows } =
    useTypedLoaderData<typeof loader>();

  const organization = useOrganization();
  const project = useProject();
  const environment = useEnvironment();
  const plan = useCurrentPlan();
  const maxPeriodDays = plan?.v3Subscription?.plan?.limits?.queryPeriodDays?.number;

  // Use most recent history item if available, otherwise fall back to defaults
  const initialQuery = history.length > 0 ? history[0].query : defaultQuery;
  const initialScope = history.length > 0 ? history[0].scope : "environment";
  const initialTimeFilter =
    history.length > 0
      ? {
          period: history[0].filterPeriod ?? undefined,
          from: history[0].filterFrom ? toISOString(history[0].filterFrom) : undefined,
          to: history[0].filterTo ? toISOString(history[0].filterTo) : undefined,
        }
      : undefined;

  // Build the query action URL for this page
  const queryActionUrl = queryPath(
    { slug: organization.slug },
    { slug: project.slug },
    { slug: environment.slug }
  );

  return (
    <QueryEditor
      defaultQuery={initialQuery}
      defaultScope={initialScope}
      defaultPeriod={defaultPeriod}
      defaultTimeFilter={initialTimeFilter}
      history={history}
      isAdmin={isAdmin}
      maxRows={maxRows}
      queryActionUrl={queryActionUrl}
      mode={{ type: "standalone" }}
      maxPeriodDays={maxPeriodDays}
    />
  );
}
