import { useCallback, useEffect, useRef, useState } from "react";
import { createFileRoute, Link } from "@tanstack/react-router";
import { useCustomer, useListPlans } from "autumn-js/react";
import { trackEvent } from "@executor-js/react/api/analytics";
import { Button } from "@executor-js/react/components/button";
import { Badge } from "@executor-js/react/components/badge";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@executor-js/react/components/dialog";

type Plan = NonNullable<ReturnType<typeof useListPlans>["data"]>[number];

export const Route = createFileRoute("/{-$orgSlug}/billing_/plans")({
  component: PlansPage,
});

// Marker appended to the checkout success URL so the page knows, on return, that
// it just came back from Stripe and which plan was attached.
const CHECKOUT_RETURN_PARAM = "checkout";

/**
 * Refresh billing data after returning from a redirect checkout.
 *
 * autumn-js fetches the customer once on load and does not refetch on its own
 * (staleTime 60s, no refetch on focus), while Stripe's `checkout.session.completed`
 * webhook reaches Autumn moments AFTER the browser is redirected back. So the
 * single fetch on the success page sees the pre-checkout plan and the page would
 * otherwise show the old plan (and the upgrade CTA) until a manual reload.
 *
 * On detecting the return marker, poll the billing data until the attached plan
 * shows as active (or a timeout). While that reconciliation is in flight this
 * returns the attached plan id so the page can show that plan as "Activating"
 * rather than the pre-checkout upgrade CTA, which would otherwise read as if the
 * purchase did not happen. The marker is stripped immediately so a later manual
 * reload does not re-arm the poll.
 *
 * @returns the plan id being finalized, or null once it reflects (or times out).
 */
function useRefreshAfterCheckout(plans: Plan[] | undefined, refetch: () => void): string | null {
  const [finalizingPlan, setFinalizingPlan] = useState<string | null>(null);
  const plansRef = useRef(plans);
  plansRef.current = plans;
  const refetchRef = useRef(refetch);
  refetchRef.current = refetch;
  const armedAtRef = useRef(0);

  // One-shot: move the URL marker into state. Only this step is single-shot —
  // the poll below keys off the STATE, because effects do not live as long as
  // state: the shell re-runs effects shortly after mount (auth seeds a frame
  // after first paint and the suspense boundary re-reveals), and when the poll
  // lived inside this effect its re-run saw the already-stripped URL, so the
  // cleanup killed the interval and nothing ever refetched — the page sat on
  // "Activating" forever while the webhook had long landed.
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const attachedPlanId = params.get(CHECKOUT_RETURN_PARAM);
    if (!attachedPlanId) return;

    params.delete(CHECKOUT_RETURN_PARAM);
    const query = params.toString();
    window.history.replaceState({}, "", `${window.location.pathname}${query ? `?${query}` : ""}`);
    armedAtRef.current = Date.now();
    setFinalizingPlan(attachedPlanId);
  }, []);

  // Poll while a plan is finalizing. Re-armable: if the effect is recycled the
  // state still says which plan is settling, so the interval comes right back.
  // The timeout is measured from when the marker was consumed, not from the
  // current effect run, so recycles cannot extend it. refetch goes through a
  // ref so the interval always calls the CURRENT client's refetch, even after
  // the billing provider rebuilds.
  useEffect(() => {
    if (!finalizingPlan) return;

    const reflected = () =>
      plansRef.current?.find((p) => p.id === finalizingPlan)?.customerEligibility?.status ===
      "active";

    refetchRef.current();
    const interval = setInterval(() => {
      if (reflected() || Date.now() - armedAtRef.current >= 20_000) {
        clearInterval(interval);
        setFinalizingPlan(null);
        return;
      }
      refetchRef.current();
    }, 1500);
    return () => clearInterval(interval);
  }, [finalizingPlan]);

  // Drop the optimistic state the moment the refetched data reflects the plan,
  // so it does not linger until the next poll tick after the webhook lands.
  useEffect(() => {
    if (
      finalizingPlan &&
      plans?.find((p) => p.id === finalizingPlan)?.customerEligibility?.status === "active"
    ) {
      setFinalizingPlan(null);
    }
  }, [finalizingPlan, plans]);

  return finalizingPlan;
}

const ENTERPRISE_FEATURES = [
  "Self-hosted or dedicated cloud deployment support",
  "SSO / SAML & SCIM provisioning",
  "Audit logs for every tool call",
  "Dedicated support & onboarding",
  "Security reviews, DPA & SOC 2 on request",
];

// Price display is hardcoded alongside the feature copy: Team's price lives
// on its per-seat `members` item in Autumn, not the plan-level `price` the
// SDK surfaces, so there is no live field to render it from.
const PLAN_META: Record<
  string,
  {
    tagline: string;
    inherits?: string;
    features: string[];
    price: { label: string; suffix?: string };
  }
> = {
  free: {
    tagline: "For small teams getting started",
    price: { label: "$0", suffix: "USD / month" },
    features: ["Up to 3 members", "100,000 executions per month", "Unlimited sources"],
  },
  team: {
    tagline: "For growing organizations",
    price: { label: "$15", suffix: "USD / member / month" },
    features: ["Unlimited executions", "Verified domains & join by team domain"],
  },
  enterprise: {
    tagline: "For orgs with custom needs",
    inherits: "Team",
    price: { label: "Custom" },
    features: ENTERPRISE_FEATURES,
  },
};

const ACTION_LABELS: Record<string, string> = {
  activate: "Subscribe",
  upgrade: "Upgrade",
  downgrade: "Downgrade",
  none: "Current plan",
  purchase: "Purchase",
};

const PLAN_ORDER = ["free", "team", "enterprise"];

function PlansPage() {
  const {
    attach,
    openCustomerPortal,
    isLoading: customerLoading,
    refetch: refetchCustomer,
  } = useCustomer();
  const {
    data: plans,
    isLoading: plansLoading,
    isFetching,
    refetch: refetchPlans,
  } = useListPlans();
  const [loadingPlan, setLoadingPlan] = useState<string | null>(null);

  const refetchBilling = useCallback(() => {
    void refetchCustomer();
    void refetchPlans();
  }, [refetchCustomer, refetchPlans]);
  const finalizingPlan = useRefreshAfterCheckout(plans, refetchBilling);

  const isLoading = customerLoading || plansLoading;

  const selfServePlans = PLAN_ORDER.flatMap((id) =>
    (plans ?? ([] as Plan[])).filter((plan: Plan) => plan.id === id),
  );

  return (
    <div className="min-h-0 flex-1 overflow-y-auto">
      <div className="mx-auto max-w-5xl px-6 py-10 lg:px-10 lg:py-14">
        <div className="mb-8">
          <Link
            to="/{-$orgSlug}/billing"
            className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors mb-4"
          >
            <svg viewBox="0 0 16 16" fill="none" className="size-3.5">
              <path
                d="M10 4L6 8l4 4"
                stroke="currentColor"
                strokeWidth="1.3"
                strokeLinecap="round"
                strokeLinejoin="round"
              />
            </svg>
            Billing
          </Link>
          <h1 className="font-display text-3xl tracking-tight text-foreground lg:text-4xl">
            Choose a plan
          </h1>
          <p className="mt-1.5 text-sm text-muted-foreground">
            Pick the plan that works for you. Upgrade or downgrade anytime.
          </p>
        </div>

        {isLoading ? (
          <div className="grid gap-4 grid-cols-1 md:grid-cols-3">
            <div className="h-64 animate-pulse rounded-xl bg-muted" />
            <div className="h-64 animate-pulse rounded-xl bg-muted" />
            <div className="h-64 animate-pulse rounded-xl bg-muted" />
          </div>
        ) : (
          <div
            className={[
              "grid gap-4 grid-cols-1 md:grid-cols-3 transition-opacity",
              isFetching ? "opacity-50 pointer-events-none" : "",
            ].join(" ")}
          >
            {selfServePlans.map((plan: Plan) => {
              const meta = PLAN_META[plan.id];
              if (!meta) return null;

              const eligibility = plan.customerEligibility;
              const action = eligibility?.attachAction ?? "activate";
              const status = eligibility?.status;
              const isCanceling = eligibility?.canceling ?? false;
              const isCurrent = status === "active" && !isCanceling;
              const isScheduled = status === "scheduled";
              const isUpgradeAction = action === "upgrade" || action === "activate";
              const isEnterprise = plan.id === "enterprise";
              // Just back from checkout for this plan and the webhook has not
              // landed yet: show it as activating instead of the stale CTA.
              const isFinalizing = plan.id === finalizingPlan && !isCurrent && !isScheduled;
              // Offer the trial only when the plan defines one and this customer
              // is still eligible (trialAvailable is false once they've used it).
              const freeTrial = plan.freeTrial;
              const trialOffered =
                freeTrial != null &&
                eligibility?.trialAvailable !== false &&
                (action === "activate" || action === "upgrade");
              const label = isCanceling
                ? "Resume"
                : trialOffered
                  ? "Start free trial"
                  : (ACTION_LABELS[action] ?? "Select");

              return (
                <div
                  key={plan.id}
                  className={[
                    "flex flex-col rounded-xl border p-5",
                    isCurrent
                      ? "border-border bg-muted"
                      : isScheduled
                        ? "border-border bg-muted"
                        : "border-border",
                  ].join(" ")}
                >
                  <div className="flex h-6 items-center justify-between">
                    <p className="text-base font-semibold text-foreground leading-none">
                      {plan.name}
                    </p>
                    {isCurrent && <Badge className="bg-muted text-foreground">Your plan</Badge>}
                    {isFinalizing && (
                      <Badge className="bg-primary/10 text-primary">Activating</Badge>
                    )}
                    {isCanceling && (
                      <Badge className="bg-muted text-muted-foreground">Canceling</Badge>
                    )}
                    {isScheduled && <Badge className="bg-muted text-foreground">Scheduled</Badge>}
                  </div>
                  <p className="mt-1 text-sm text-muted-foreground">{meta.tagline}</p>

                  <div className="mt-4 flex items-baseline gap-1.5">
                    <span className="text-2xl font-semibold text-foreground tabular-nums">
                      {meta.price.label}
                    </span>
                    {meta.price.suffix && (
                      <span className="text-sm text-muted-foreground">{meta.price.suffix}</span>
                    )}
                  </div>

                  <div className="mt-4">
                    {(isCurrent && !isCanceling) || isScheduled ? (
                      <div className="flex h-9 items-center justify-center rounded-md border border-border bg-muted/30 text-sm font-medium text-muted-foreground">
                        {isCurrent ? "Current plan" : "Scheduled"}
                      </div>
                    ) : isFinalizing ? (
                      <div className="flex h-9 items-center justify-center gap-2 rounded-md border border-border bg-muted/30 text-sm font-medium text-muted-foreground">
                        <span className="size-3.5 animate-spin rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground" />
                        Activating…
                      </div>
                    ) : isEnterprise ? (
                      <EnterpriseContactDialog />
                    ) : isCanceling ? (
                      <Button
                        type="button"
                        disabled={loadingPlan !== null}
                        onClick={() => openCustomerPortal()}
                        className="flex h-9 w-full items-center justify-center rounded-md bg-primary text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-60"
                      >
                        Resume
                      </Button>
                    ) : (
                      <Button
                        type="button"
                        disabled={loadingPlan !== null}
                        onClick={async () => {
                          // Before attach(): it assigns window.location.href to
                          // the checkout URL while resolving, so anything after
                          // the await races the page unload.
                          if (
                            action === "activate" ||
                            action === "upgrade" ||
                            action === "downgrade"
                          ) {
                            trackEvent("billing_plan_selected", {
                              plan_id: plan.id,
                              action: action as "activate" | "upgrade" | "downgrade",
                            });
                          }
                          setLoadingPlan(plan.id);
                          // Tag the return URL so the page refetches billing
                          // data when Stripe redirects back (the webhook that
                          // activates the plan lands moments after the redirect).
                          const successUrl = `${window.location.origin}${window.location.pathname}?${CHECKOUT_RETURN_PARAM}=${plan.id}`;
                          await attach({ planId: plan.id, redirectMode: "always", successUrl });
                          setLoadingPlan(null);
                        }}
                        className={[
                          "flex h-9 w-full items-center justify-center rounded-md text-sm font-medium transition-colors disabled:opacity-60",
                          isUpgradeAction
                            ? "bg-primary text-primary-foreground hover:bg-primary/90"
                            : "border border-border bg-background text-foreground hover:bg-muted",
                        ].join(" ")}
                      >
                        {loadingPlan === plan.id ? "Loading…" : label}
                      </Button>
                    )}
                  </div>

                  {meta.inherits && (
                    <p className="mt-5 text-xs font-medium text-foreground">
                      Everything in {meta.inherits}, plus
                    </p>
                  )}
                  <ul
                    role="list"
                    className={["space-y-2", meta.inherits ? "mt-2" : "mt-5"].join(" ")}
                  >
                    {meta.features.map((f) => (
                      <li key={f} className="flex items-start gap-2 text-xs text-muted-foreground">
                        <svg
                          viewBox="0 0 16 16"
                          fill="none"
                          className="mt-px size-3.5 shrink-0 text-primary/60"
                        >
                          <path
                            d="M3.5 8.5L6.5 11.5L12.5 5"
                            stroke="currentColor"
                            strokeWidth="1.5"
                            strokeLinecap="round"
                            strokeLinejoin="round"
                          />
                        </svg>
                        <span>{f}</span>
                      </li>
                    ))}
                  </ul>
                </div>
              );
            })}
          </div>
        )}

        <SlackContactCta />
      </div>
    </div>
  );
}

function EnterpriseContactDialog() {
  const [open, setOpen] = useState(false);

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogTrigger asChild>
        <Button
          type="button"
          variant="outline"
          className="flex h-9 w-full items-center justify-center rounded-md text-sm font-medium"
        >
          Contact us
        </Button>
      </DialogTrigger>
      {/* Static contact details only: clicking away dismisses it. */}
      <DialogContent dismissOnOutsideClick>
        <DialogHeader>
          <DialogTitle>Talk to us about Enterprise</DialogTitle>
          <DialogDescription>
            Add{" "}
            <a className="font-medium text-foreground underline" href="mailto:rhys@executor.sh">
              rhys@executor.sh
            </a>{" "}
            on Slack Connect and send the org name, team size, and anything you need covered.
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose asChild>
            <Button type="button" variant="outline">
              Done
            </Button>
          </DialogClose>
          <Button asChild>
            <a href="mailto:rhys@executor.sh?subject=Executor%20Enterprise%20inquiry">
              Email instead
            </a>
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function SlackContactCta() {
  const [open, setOpen] = useState(false);

  return (
    <div className="mt-10 flex flex-col items-center gap-3 border-t border-border pt-8 text-center">
      <p className="text-sm text-muted-foreground">Got questions?</p>
      <div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-sm font-medium">
        <Dialog open={open} onOpenChange={setOpen}>
          <DialogTrigger asChild>
            <Button
              type="button"
              variant="ghost"
              size="sm"
              className="text-foreground hover:text-primary"
            >
              <SlackMark className="size-4" />
              Get in touch on Slack
              <span aria-hidden>→</span>
            </Button>
          </DialogTrigger>
          {/* Static contact details only: clicking away dismisses it. */}
          <DialogContent dismissOnOutsideClick>
            <DialogHeader>
              <DialogTitle>Get in touch on Slack</DialogTitle>
              <DialogDescription>
                Add{" "}
                <a className="font-medium text-foreground underline" href="mailto:rhys@executor.sh">
                  rhys@executor.sh
                </a>{" "}
                on Slack Connect to get in touch.
              </DialogDescription>
            </DialogHeader>
            <DialogFooter>
              <DialogClose asChild>
                <Button type="button" variant="outline">
                  Done
                </Button>
              </DialogClose>
              <Button asChild>
                <a href="mailto:rhys@executor.sh?subject=Executor%20Slack%20invite">
                  Email for invite
                </a>
              </Button>
            </DialogFooter>
          </DialogContent>
        </Dialog>
        <span className="text-muted-foreground/60" aria-hidden>
          ·
        </span>
        <a
          href="mailto:rhys@executor.sh?subject=Executor%20question"
          className="inline-flex items-center gap-1.5 text-foreground hover:text-primary transition-colors"
        >
          <MailIcon className="size-4" />
          Email us
          <span aria-hidden>→</span>
        </a>
      </div>
    </div>
  );
}

function MailIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="1.8"
      className={className}
      aria-hidden
    >
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        d="M3 7l9 6 9-6M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
      />
    </svg>
  );
}

function SlackMark({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 24 24" fill="none" className={className} aria-hidden>
      <path
        d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z"
        fill="#E01E5A"
      />
      <path
        d="M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.527 2.527 0 0 1 2.521 2.521 2.527 2.527 0 0 1-2.521 2.521H2.522A2.527 2.527 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z"
        fill="#36C5F0"
      />
      <path
        d="M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.272 0a2.528 2.528 0 0 1-2.521 2.521 2.527 2.527 0 0 1-2.521-2.521V2.522A2.527 2.527 0 0 1 15.163 0a2.528 2.528 0 0 1 2.521 2.522v6.312z"
        fill="#2EB67D"
      />
      <path
        d="M15.163 18.956a2.528 2.528 0 0 1 2.521 2.522A2.528 2.528 0 0 1 15.163 24a2.527 2.527 0 0 1-2.521-2.522v-2.522h2.521zm0-1.272a2.527 2.527 0 0 1-2.521-2.521 2.527 2.527 0 0 1 2.521-2.521h6.315A2.527 2.527 0 0 1 24 15.163a2.528 2.528 0 0 1-2.522 2.521h-6.315z"
        fill="#ECB22E"
      />
    </svg>
  );
}
