"use client";

import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import type { PackageDef } from "@/lib/packages";
import type {
  SkillActivationRequest,
  SkillPricing,
} from "@/types";
import { SkillCostDialog } from "./skill-cost-dialog";

interface Props {
  pkg: PackageDef;
  enabled: boolean;
  status?: "pending" | "active" | "error";
  tenantName: string;
  onToggled: () => void;
  /** Slice 5: when false, the enable/disable button is hidden. */
  canEdit?: boolean;
  /**
   * Phase 2.5 — most recent non-terminal activation request for this
   * skill on this tenant, if any. Drives the "Manual review pending"
   * and "Activation rejected" inline states. Approved/withdrawn rows
   * never reach the client side.
   */
  activationRequest?: SkillActivationRequest | null;
  /**
   * Phase 2.5 — pricing for this skill if it has any. Triggers the
   * cost-disclosure dialog before enable.
   */
  pricing?: SkillPricing | null;
}

export function PackageCard({
  pkg,
  enabled,
  status,
  tenantName,
  onToggled,
  canEdit = true,
  activationRequest = null,
  pricing = null,
}: Props) {
  const t = useTranslations();
  const router = useRouter();
  const [showModal, setShowModal] = useState(false);
  const [secrets, setSecrets] = useState<Record<string, string>>({});
  const [accepted, setAccepted] = useState(false);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);
  // Phase 2.5: cost-disclosure flow + activation-request flow.
  const [showCostDialog, setShowCostDialog] = useState(false);
  const isPriced =
    (pricing?.dailyPriceChf ?? 0) > 0 || (pricing?.setupFeeChf ?? 0) > 0;

  function handleEnable() {
    // Phase 2.5: gate priced skills behind the cost-disclosure dialog.
    // Confirm → proceedWithEnable. Cancel → bail.
    if (isPriced) {
      setError(null);
      setShowCostDialog(true);
      return;
    }
    void proceedWithEnable();
  }

  async function proceedWithEnable() {
    if (pkg.customProvisioning) {
      // Platform-side provisioning, then add to packages list.
      setSaving(true);
      setError(null);
      try {
        const provRes = await fetch(`/api/tenants/${tenantName}/${pkg.id}`, {
          method: "POST",
        });
        if (!provRes.ok) {
          const err = await provRes.json().catch(() => ({}));
          throw new Error(err.error || `Provisioning failed (HTTP ${provRes.status})`);
        }
        await togglePackage(true);
      } catch (e: any) {
        setError(e.message);
      } finally {
        setSaving(false);
      }
      return;
    }
    if (pkg.requiresSecrets) {
      setShowModal(true);
      setSecrets({});
      setAccepted(false);
      setError(null);
      return;
    }
    await togglePackage(true);
  }

  async function handleDisable() {
    setSaving(true);
    setError(null);
    try {
      if (pkg.customProvisioning) {
        // Revoke platform-side credentials FIRST so the relay drops
        // routes before the operator removes the channel from the
        // OpenClaw config. Partial-success (token revoked, OpenBao
        // delete failed) returns 503 with partial=true and we surface
        // the error rather than continuing — the secret may still be
        // valid in OpenBao and rolling back the relay revoke isn't
        // possible (it cascaded to routes).
        const deprovRes = await fetch(`/api/tenants/${tenantName}/${pkg.id}`, {
          method: "DELETE",
        });
        if (!deprovRes.ok) {
          const err = await deprovRes.json().catch(() => ({}));
          throw new Error(err.error || `Deprovisioning failed (HTTP ${deprovRes.status})`);
        }
      }
      await togglePackage(false);
    } catch (e: any) {
      setError(e.message);
    } finally {
      setSaving(false);
    }
  }

  async function togglePackage(enable: boolean) {
    setSaving(true);
    try {
      const res = await fetch(`/api/tenants/${tenantName}`);
      const tenant = await res.json();
      const current: string[] = tenant.spec?.packages || [];
      const next = enable
        ? [...current, pkg.id]
        : current.filter((p: string) => p !== pkg.id);

      const patchRes = await fetch(`/api/tenants/${tenantName}`, {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ packages: next }),
      });
      if (!patchRes.ok) throw new Error("Failed to update packages");
      onToggled();
    } catch (e: any) {
      setError(e.message);
    } finally {
      setSaving(false);
    }
  }

  // Phase 2.5: withdraw a still-pending activation request. The
  // request row flips to 'withdrawn' (server-side); router.refresh()
  // re-renders the tenant page without the pending state, leaving
  // the toggle re-enabled if the user wants to retry.
  async function withdrawRequest() {
    if (!activationRequest || activationRequest.status !== "pending") return;
    setSaving(true);
    setError(null);
    try {
      const res = await fetch(
        `/api/skills/requests/${activationRequest.id}/withdraw`,
        { method: "POST" }
      );
      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err.error || `HTTP ${res.status}`);
      }
      router.refresh();
    } catch (e: any) {
      setError(e.message);
    } finally {
      setSaving(false);
    }
  }

  // Phase 2.5: retry after a rejection. Same flow as a fresh
  // enable; the rejected row stays in the DB as audit trail but a
  // new pending row will be created by the PATCH.
  function tryAgainAfterRejection() {
    setError(null);
    handleEnable();
  }

  async function handleSubmitSecrets() {
    if (pkg.disclaimerKey && !accepted) return;

    const required = (pkg.secrets || []).map((s) => s.key);
    const missing = required.filter((k) => !secrets[k]?.trim());
    if (missing.length) { setError(t("packages.missingFields")); return; }

    setSaving(true);
    setError(null);
    try {
      const secretRes = await fetch(`/api/tenants/${tenantName}/secrets`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ packageId: pkg.id, secrets }),
      });
      if (!secretRes.ok) {
        const err = await secretRes.json();
        throw new Error(err.error || "Failed to store secrets");
      }
      await togglePackage(true);
      setShowModal(false);
    } catch (e: any) {
      setError(e.message);
    } finally {
      setSaving(false);
    }
  }

  const statusColors: Record<string, string> = {
    pending: "text-warning",
    active: "text-success",
    error: "text-error",
  };

  return (
    <>
      <div className="bg-surface-1 border border-border rounded-xl p-5 flex flex-col gap-3">
        <div className="flex items-start justify-between">
          <div>
            <div className="flex items-center gap-2">
              <span className="text-sm font-medium text-text-primary">{pkg.name}</span>
              <span className="text-[10px] font-semibold uppercase tracking-wider text-text-muted bg-surface-3 px-1.5 py-0.5 rounded">
                {pkg.category}
              </span>
            </div>
            <p className="text-xs text-text-secondary mt-1">{t(pkg.descriptionKey)}</p>
          </div>
          {enabled && status && (
            <span className={`text-xs font-medium ${statusColors[status] || ""}`}>
              {t(`packages.status.${status}`)}
            </span>
          )}
        </div>

        <div className="flex items-center justify-between mt-auto pt-3 border-t border-border">
          {pkg.requiresSecrets && (
            <span className="text-[10px] text-text-muted">{t("packages.requiresApiKey")}</span>
          )}
          {/* Phase 2.5: pending or rejected request takes precedence
              over the toggle. Approved/withdrawn never reach here.
              For packages that needed secrets, surface that they're
              safely stored — the user might otherwise worry the
              credentials they typed got lost when the activation
              was deferred. */}
          {canEdit && activationRequest?.status === "pending" ? (
            <div className="ml-auto flex flex-col items-end gap-1">
              <span
                className="text-[10px] text-warning italic"
                title={pkg.requiresSecrets ? t("packages.credentialsSavedTip") : undefined}
              >
                {t("packages.manualReviewPending")}
                {pkg.requiresSecrets && (
                  <span className="text-text-muted ml-1 not-italic">
                    · {t("packages.credentialsSaved")}
                  </span>
                )}
              </span>
              <button
                onClick={withdrawRequest}
                disabled={saving}
                className="rounded-lg px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary bg-surface-3 hover:bg-surface-2 disabled:opacity-50 cursor-pointer"
              >
                {saving ? "…" : t("packages.withdraw")}
              </button>
            </div>
          ) : canEdit && activationRequest?.status === "rejected" ? (
            <div className="ml-auto flex flex-col items-end gap-1">
              <span
                className="text-[10px] text-error italic max-w-[220px] truncate"
                title={activationRequest.rejectionReason ?? ""}
              >
                {t("packages.activationRejected")}: {activationRequest.rejectionReason}
              </span>
              <button
                onClick={tryAgainAfterRejection}
                disabled={saving}
                className="rounded-lg px-3 py-1.5 text-xs font-medium bg-accent text-surface-0 hover:bg-accent-dim disabled:opacity-50 cursor-pointer shadow-lg shadow-accent/20"
              >
                {saving ? "…" : t("packages.tryAgain")}
              </button>
            </div>
          ) : canEdit ? (
            <button
              onClick={enabled ? handleDisable : handleEnable}
              disabled={saving}
              className={`ml-auto rounded-lg px-3 py-1.5 text-xs font-medium transition-all cursor-pointer ${
                enabled
                  ? "bg-surface-3 text-text-secondary hover:text-text-primary hover:bg-surface-2"
                  : "bg-accent text-surface-0 hover:bg-accent-dim shadow-lg shadow-accent/20"
              } disabled:opacity-50`}
            >
              {saving ? "…" : enabled ? t("packages.disable") : t("packages.enable")}
            </button>
          ) : (
            // Slice 5: read-only viewers see a static badge instead of a
            // toggle. The status badge above the divider already conveys
            // "active/pending/error"; this just clarifies "you can't change
            // it" without duplicating the status colour.
            <span className="ml-auto text-[10px] text-text-muted italic">
              {enabled ? t("packages.statusEnabled") : t("packages.statusDisabled")}
            </span>
          )}
        </div>
      </div>

      {/* Phase 2.5: cost-disclosure modal for priced skills. */}
      <SkillCostDialog
        open={showCostDialog}
        onClose={() => setShowCostDialog(false)}
        onConfirm={() => {
          setShowCostDialog(false);
          void proceedWithEnable();
        }}
        skillName={pkg.name}
        dailyPriceChf={pricing?.dailyPriceChf ?? 0}
        setupFeeChf={pricing?.setupFeeChf ?? 0}
        busy={saving}
      />

      {showModal && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
          <div className="w-full max-w-md bg-surface-1 border border-border rounded-2xl p-6 space-y-4 shadow-2xl shadow-black/40">
            <h3 className="font-display text-base font-semibold text-text-primary">
              {t("packages.configure")} {pkg.name}
            </h3>

            {pkg.instructionsKey && (
              <div className="bg-surface-2 border border-border rounded-lg p-3 text-xs text-text-secondary leading-relaxed whitespace-pre-line">
                {t(pkg.instructionsKey)}
              </div>
            )}

            <div className="space-y-3">
              {(pkg.secrets || []).map((s) => (
                <label key={s.key} className="block">
                  <span className="text-xs text-text-secondary mb-1 block">{t(s.labelKey)}</span>
                  <input
                    type="password"
                    placeholder={t(s.placeholderKey)}
                    value={secrets[s.key] || ""}
                    onChange={(e) => setSecrets((p) => ({ ...p, [s.key]: e.target.value }))}
                    className="w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:border-accent focus:outline-none"
                  />
                </label>
              ))}
            </div>

            {pkg.disclaimerKey && (
              <label className="flex items-start gap-2 text-xs text-text-secondary">
                <input
                  type="checkbox"
                  checked={accepted}
                  onChange={(e) => setAccepted(e.target.checked)}
                  className="mt-0.5 accent-accent"
                />
                <span>{t(pkg.disclaimerKey)}</span>
              </label>
            )}

            {error && <p className="text-xs text-error">{error}</p>}

            <div className="flex justify-end gap-2 pt-2">
              <button
                onClick={() => setShowModal(false)}
                className="rounded-lg px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary cursor-pointer"
              >
                {t("common.cancel")}
              </button>
              <button
                onClick={handleSubmitSecrets}
                disabled={saving || (!!pkg.disclaimerKey && !accepted)}
                className="rounded-lg bg-accent px-4 py-1.5 text-xs font-medium text-surface-0 hover:bg-accent-dim disabled:opacity-50 cursor-pointer shadow-lg shadow-accent/20"
              >
                {saving ? "…" : t("packages.enableAndSave")}
              </button>
            </div>
          </div>
        </div>
      )}
    </>
  );
}
