import { useCallback, useEffect, useState } from "react"
import { Check, RefreshCw, Trash2 } from "lucide-react"

import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-core-client"
import { AlertBanner } from "@crema/feedback-ui"

import { AppShell } from "~/components/layout/app-shell"
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"
import { Badge } from "~/components/ui/badge"
import { Button } from "~/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "~/components/ui/card"
import { Input } from "~/components/ui/input"
import { Textarea } from "~/components/ui/textarea"
import { pageTitle } from "~/lib/page-meta"
import {
  profileInitials,
  saveProfile,
  useProfile,
  type Profile,
} from "~/lib/profile"
import { getUser, updateUser, type User } from "~/lib/arcadia/users"
import {
  fetchDigitalObjectAsBlobUrl,
  uploadFile,
} from "~/lib/arcadia/digital-objects"
import {
  getProfile,
  updateProfile as updateArcadiaProfile,
  pickAvatarUrl,
  type Profile as ArcadiaProfile,
} from "~/lib/arcadia/profiles"
import { updateSessionUser, useSession } from "~/lib/session"

export const meta = () => pageTitle("Profile")

interface AccountDraft {
  first_name: string
  last_name: string
  email: string
}

export default function ProfileRoute() {
  const session = useSession()
  const arcadia = useArcadiaClient()
  const profile = useProfile()

  // Mirror of the resolved avatar URL — kept in localStorage so the
  // <AvatarImage> in the appbar can render before the profile fetch
  // resolves on next mount.
  const [prefs, setPrefs] = useState<Profile>(profile)
  useEffect(() => {
    setPrefs(profile)
  }, [profile])

  // Arcadia account.
  const [account, setAccount] = useState<User | null>(null)
  const [accountDraft, setAccountDraft] = useState<AccountDraft>({
    first_name: "",
    last_name: "",
    email: "",
  })
  const [accountLoading, setAccountLoading] = useState(true)
  const [accountSaving, setAccountSaving] = useState(false)
  const [accountSavedAt, setAccountSavedAt] = useState<number | null>(null)
  const [accountError, setAccountError] = useState<string | null>(null)

  // Server-side profile (avatar lives here — `prefs.avatarUrl` mirrors
  // the resolved URL so the existing <AvatarImage> bindings keep working).
  const [arcadiaProfile, setArcadiaProfile] = useState<ArcadiaProfile | null>(null)
  const [avatarUploading, setAvatarUploading] = useState(false)
  const [avatarError, setAvatarError] = useState<string | null>(null)

  // Public-profile editable fields, server-backed via PATCH /api/v1/profile.
  const [profileDraft, setProfileDraft] = useState<{
    bio: string
    phone: string
    location: string
    timezone: string
  }>({ bio: "", phone: "", location: "", timezone: "" })
  const [profileSaving, setProfileSaving] = useState(false)
  const [profileSavedAt, setProfileSavedAt] = useState<number | null>(null)
  const [profileError, setProfileError] = useState<string | null>(null)

  const profileDirty =
    !!arcadiaProfile &&
    (profileDraft.bio !== (arcadiaProfile.bio ?? "") ||
      profileDraft.phone !== (arcadiaProfile.phone ?? "") ||
      profileDraft.location !== (arcadiaProfile.location ?? "") ||
      profileDraft.timezone !== (arcadiaProfile.timezone ?? ""))

  const loadAccount = useCallback(async () => {
    if (!session) return
    setAccountLoading(true)
    setAccountError(null)
    try {
      const [u, p] = await Promise.all([
        getUser(arcadia, session.userId),
        getProfile(arcadia).catch(() => null),
      ])
      setAccount(u)
      setAccountDraft({
        first_name: u.first_name ?? "",
        last_name: u.last_name ?? "",
        email: u.email,
      })
      if (p) {
        setArcadiaProfile(p)
        setProfileDraft({
          bio: p.bio ?? "",
          phone: p.phone ?? "",
          location: p.location ?? "",
          timezone: p.timezone ?? "",
        })
        const url = pickAvatarUrl(p)
        if (url) {
          // Persist into localStorage so the appbar's useProfile() picks
          // it up on next render — without this, the appbar avatar reverts
          // to initials on every fresh browser session until the user
          // re-uploads.
          setPrefs((d) => {
            const next = { ...d, avatarUrl: url }
            saveProfile(next)
            return next
          })
        }
      }
    } catch (err) {
      setAccountError(
        err instanceof ArcadiaError ? err.message : "Failed to load account.",
      )
    } finally {
      setAccountLoading(false)
    }
  }, [arcadia, session])

  useEffect(() => {
    loadAccount()
  }, [loadAccount])

  const accountDirty =
    !!account &&
    (accountDraft.first_name !== (account.first_name ?? "") ||
      accountDraft.last_name !== (account.last_name ?? "") ||
      accountDraft.email !== account.email)

  const saveAccount = async () => {
    if (!account) return
    setAccountSaving(true)
    setAccountError(null)
    try {
      const updated = await updateUser(arcadia, account.id, {
        first_name: accountDraft.first_name || null,
        last_name: accountDraft.last_name || null,
        email: accountDraft.email,
      })
      setAccount(updated)
      updateSessionUser({ name: updated.full_name, email: updated.email })
      setAccountSavedAt(Date.now())
    } catch (err) {
      setAccountError(
        err instanceof ArcadiaError ? err.message : "Save failed.",
      )
    } finally {
      setAccountSaving(false)
    }
  }

  const saveArcadiaProfile = async () => {
    setProfileSaving(true)
    setProfileError(null)
    try {
      const updated = await updateArcadiaProfile(arcadia, {
        bio: profileDraft.bio || null,
        phone: profileDraft.phone || null,
        location: profileDraft.location || null,
        timezone: profileDraft.timezone || null,
      })
      setArcadiaProfile(updated)
      setProfileDraft({
        bio: updated.bio ?? "",
        phone: updated.phone ?? "",
        location: updated.location ?? "",
        timezone: updated.timezone ?? "",
      })
      setProfileSavedAt(Date.now())
    } catch (err) {
      setProfileError(
        err instanceof ArcadiaError
          ? err.message
          : err instanceof Error
            ? err.message
            : "Save failed.",
      )
    } finally {
      setProfileSaving(false)
    }
  }

  // Local prefs handlers.
  const initials = profileInitials(
    [accountDraft.first_name, accountDraft.last_name].filter(Boolean).join(" ") ||
      account?.full_name ||
      session?.name ||
      "",
  )

  const onPickAvatar = async (file: File | null) => {
    setAvatarError(null)

    if (!file) {
      // Clear: detach the digital object on the server, then drop the
      // local cache. Keep the local cache cleared even if the server call
      // fails so the UI reflects the user's intent.
      setPrefs((d) => ({ ...d, avatarUrl: "" }))
      try {
        const updated = await updateArcadiaProfile(arcadia, {
          avatar_digital_object_id: null,
        })
        setArcadiaProfile(updated)
        savePrefsLocal({ ...prefs, avatarUrl: "" })
      } catch (err) {
        setAvatarError(
          err instanceof Error ? err.message : "Failed to clear avatar.",
        )
      }
      return
    }

    if (!file.type.startsWith("image/")) {
      setAvatarError("Avatar must be an image (PNG, JPG, GIF, WebP).")
      return
    }
    // 8MB hard cap client-side; arcadia will enforce its own quota too.
    if (file.size > 8 * 1024 * 1024) {
      setAvatarError("Avatar is too large (max 8MB).")
      return
    }

    setAvatarUploading(true)
    try {
      const obj = await uploadFile(arcadia, file, { tags: ["avatar"] })
      const updated = await updateArcadiaProfile(arcadia, {
        avatar_digital_object_id: obj.id,
      })
      setArcadiaProfile(updated)
      const persistentUrl = pickAvatarUrl(updated)
      if (persistentUrl) {
        // Variant pipeline already finished — persist to localStorage.
        const next = { ...prefs, avatarUrl: persistentUrl }
        setPrefs(next)
        savePrefsLocal(next)
      } else {
        // Variants aren't ready yet (image-processing is async). Fetch
        // the raw object as a blob URL for immediate in-memory render.
        // Don't persist to localStorage — blob URLs don't survive a
        // reload, and ProfileBootstrap will pick up the persistent URL
        // on next mount once processing completes.
        const token =
          typeof window !== "undefined"
            ? sessionStorage.getItem("arcadia_access_token")
            : null
        if (token) {
          const baseUrl =
            (import.meta.env.VITE_ARCADIA_URL as string | undefined) ??
            "http://localhost:4000"
          const tenantId =
            (import.meta.env.VITE_ARCADIA_TENANT as string | undefined) ??
            "default"
          try {
            const blobUrl = await fetchDigitalObjectAsBlobUrl(
              baseUrl,
              obj.id,
              token,
              tenantId,
            )
            // eslint-disable-next-line no-console
            console.info("[avatar] blob URL ready:", blobUrl)
            // Persist the blob URL so the appbar's useProfile() picks it
            // up via the storage event. Blob URLs don't survive a reload,
            // but ProfileBootstrap will refresh on next mount.
            const next = { ...prefs, avatarUrl: blobUrl }
            setPrefs(next)
            savePrefsLocal(next)
          } catch (e) {
            // eslint-disable-next-line no-console
            console.error("[avatar] blob fetch failed:", e)
          }
        }
      }
    } catch (err) {
      setAvatarError(
        err instanceof Error ? err.message : "Avatar upload failed.",
      )
    } finally {
      setAvatarUploading(false)
    }
  }

  // Mirror the avatar URL into localStorage so it survives reloads.
  const savePrefsLocal = (next: Profile) => {
    saveProfile(next)
  }

  return (
    <AppShell>
      <Card>
        <CardHeader>
          <CardTitle className="flex items-center gap-3">
            Account
            {account?.email_verified ? (
              <Badge variant="default">Verified</Badge>
            ) : account ? (
              <Badge variant="secondary">Unverified</Badge>
            ) : null}
            {account?.status && account.status !== "active" ? (
              <Badge variant="destructive">{account.status}</Badge>
            ) : null}
          </CardTitle>
          <CardDescription>
            Your arcadia identity. Changes are saved to the platform and reflected
            anywhere your name or email appears.
          </CardDescription>
        </CardHeader>
        <CardContent className="flex flex-col gap-6">
          {accountError ? (
            <AlertBanner
              variant="error"
              dismissible
              onDismiss={() => setAccountError(null)}
            >
              {accountError}
            </AlertBanner>
          ) : null}

          <div className="flex flex-wrap items-center gap-4">
            <Avatar className="size-20 ring-2 ring-primary/30">
              {prefs.avatarUrl ? (
                <AvatarImage
                  key={prefs.avatarUrl}
                  src={prefs.avatarUrl}
                  alt={accountDraft.email}
                />
              ) : null}
              <AvatarFallback className="bg-primary text-lg font-semibold text-primary-foreground">
                {initials}
              </AvatarFallback>
            </Avatar>
            <div className="flex flex-col gap-1 text-sm">
              <span className="font-medium">
                {account?.full_name || accountDraft.email || "—"}
              </span>
              {account ? (
                <>
                  <span className="text-xs text-muted-foreground">
                    Tenant <code className="font-mono">{account.tenant_id}</code> ·
                    ID <code className="font-mono">{account.id}</code>
                  </span>
                  <span className="text-xs text-muted-foreground">
                    Last sign-in{" "}
                    {account.last_sign_in_at
                      ? new Date(account.last_sign_in_at).toLocaleString()
                      : "—"}
                  </span>
                </>
              ) : null}
            </div>
          </div>

          <div className="grid gap-4 md:grid-cols-2">
            <Field label="First name">
              <Input
                data-action="profile-first-name"
                value={accountDraft.first_name}
                onChange={(e) =>
                  setAccountDraft((d) => ({ ...d, first_name: e.target.value }))
                }
                autoComplete="given-name"
                disabled={accountLoading || accountSaving}
              />
            </Field>
            <Field label="Last name">
              <Input
                data-action="profile-last-name"
                value={accountDraft.last_name}
                onChange={(e) =>
                  setAccountDraft((d) => ({ ...d, last_name: e.target.value }))
                }
                autoComplete="family-name"
                disabled={accountLoading || accountSaving}
              />
            </Field>
            <Field
              label="Email"
              hint="Updating your email may require re-verification."
            >
              <Input
                data-action="profile-email"
                type="email"
                value={accountDraft.email}
                onChange={(e) =>
                  setAccountDraft((d) => ({ ...d, email: e.target.value }))
                }
                autoComplete="email"
                disabled={accountLoading || accountSaving}
              />
            </Field>
          </div>

          <div className="flex flex-wrap items-center gap-2">
            <Button
              data-action="profile-account-save"
              onClick={saveAccount}
              disabled={!accountDirty || accountSaving || accountLoading}
            >
              {accountSaving ? (
                <RefreshCw className="size-4 animate-spin" />
              ) : null}
              Save account
            </Button>
            <Button
              data-action="profile-account-revert"
              variant="ghost"
              onClick={() => {
                if (!account) return
                setAccountDraft({
                  first_name: account.first_name ?? "",
                  last_name: account.last_name ?? "",
                  email: account.email,
                })
              }}
              disabled={!accountDirty || accountSaving}
            >
              Revert
            </Button>
            <Button
              data-action="profile-account-refresh"
              variant="ghost"
              onClick={loadAccount}
              disabled={accountLoading}
            >
              <RefreshCw
                className={accountLoading ? "size-4 animate-spin" : "size-4"}
              />
              Refresh
            </Button>
            {accountSavedAt && !accountDirty && (
              <span className="inline-flex items-center gap-1 text-sm text-emerald-700 dark:text-emerald-400">
                <Check className="size-4" /> Saved.
              </span>
            )}
          </div>
        </CardContent>
      </Card>

      <Card>
        <CardHeader>
          <CardTitle>Profile</CardTitle>
          <CardDescription>
            Public profile fields stored on arcadia. Visible to other members
            of this tenant.
          </CardDescription>
        </CardHeader>
        <CardContent className="flex flex-col gap-4">
          {profileError ? (
            <AlertBanner variant="error">{profileError}</AlertBanner>
          ) : null}
          <Field
            label="Bio"
            hint="A short blurb about you."
          >
            <Textarea
              data-action="profile-bio"
              value={profileDraft.bio}
              onChange={(e) =>
                setProfileDraft((d) => ({ ...d, bio: e.target.value }))
              }
              rows={3}
              placeholder="Tell others what you work on."
            />
          </Field>
          <div className="grid gap-4 md:grid-cols-2">
            <Field label="Phone">
              <Input
                data-action="profile-phone"
                value={profileDraft.phone}
                onChange={(e) =>
                  setProfileDraft((d) => ({ ...d, phone: e.target.value }))
                }
                placeholder="+61 …"
              />
            </Field>
            <Field label="Location">
              <Input
                data-action="profile-location"
                value={profileDraft.location}
                onChange={(e) =>
                  setProfileDraft((d) => ({ ...d, location: e.target.value }))
                }
                placeholder="Melbourne, AU"
              />
            </Field>
            <Field
              label="Timezone"
              hint="IANA name (e.g. Australia/Melbourne)."
            >
              <Input
                data-action="profile-timezone"
                value={profileDraft.timezone}
                onChange={(e) =>
                  setProfileDraft((d) => ({ ...d, timezone: e.target.value }))
                }
                placeholder="Australia/Melbourne"
              />
            </Field>
          </div>
          <div className="flex flex-wrap items-center gap-2">
            <Button
              data-action="profile-arcadia-save"
              onClick={saveArcadiaProfile}
              disabled={!profileDirty || profileSaving}
            >
              {profileSaving ? "Saving…" : "Save profile"}
            </Button>
            <Button
              data-action="profile-arcadia-revert"
              variant="ghost"
              onClick={() => {
                if (!arcadiaProfile) return
                setProfileDraft({
                  bio: arcadiaProfile.bio ?? "",
                  phone: arcadiaProfile.phone ?? "",
                  location: arcadiaProfile.location ?? "",
                  timezone: arcadiaProfile.timezone ?? "",
                })
              }}
              disabled={!profileDirty || profileSaving}
            >
              Revert
            </Button>
            {profileSavedAt && !profileDirty ? (
              <span className="inline-flex items-center gap-1 text-sm text-emerald-700 dark:text-emerald-400">
                <Check className="size-4" /> Saved.
              </span>
            ) : null}
          </div>
        </CardContent>
      </Card>

      <Card>
        <CardHeader>
          <CardTitle>Avatar</CardTitle>
          <CardDescription>
            Uploads land in your tenant's storage backend.
          </CardDescription>
        </CardHeader>
        <CardContent className="flex flex-col gap-2">
          {avatarError ? (
            <AlertBanner variant="error">{avatarError}</AlertBanner>
          ) : null}
          <div className="flex items-center gap-3">
            <label
              aria-disabled={avatarUploading}
              className={[
                "inline-flex w-fit items-center gap-2 rounded-md border bg-background px-3 py-1.5 text-sm",
                avatarUploading
                  ? "cursor-not-allowed opacity-60"
                  : "cursor-pointer hover:bg-accent hover:text-accent-foreground",
              ].join(" ")}
            >
              <input
                data-action="profile-avatar-upload"
                type="file"
                accept="image/*"
                className="sr-only"
                disabled={avatarUploading}
                onChange={(e) => onPickAvatar(e.target.files?.[0] ?? null)}
              />
              {avatarUploading ? "Uploading…" : "Upload avatar"}
            </label>
            {prefs.avatarUrl && !avatarUploading && (
              <Button
                data-action="profile-avatar-remove"
                variant="ghost"
                size="sm"
                onClick={() => onPickAvatar(null)}
                className="text-muted-foreground"
              >
                <Trash2 className="size-3.5" /> Remove
              </Button>
            )}
          </div>
          <span className="text-xs text-muted-foreground">
            PNG, JPG, GIF, or WebP. Max 8MB.
          </span>
        </CardContent>
      </Card>
    </AppShell>
  )
}

function Field({
  label,
  hint,
  children,
}: {
  label: string
  hint?: string
  children: React.ReactNode
}) {
  return (
    <label className="flex flex-col gap-1.5">
      <span className="text-sm font-medium">{label}</span>
      {children}
      {hint && <span className="text-xs text-muted-foreground">{hint}</span>}
    </label>
  )
}

export { RouteErrorBoundary as ErrorBoundary } from "~/components/route-error"
