"use client";

import Link from "next/link";
import { Suspense, useEffect, useRef, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { AppShell, ScreenHeader, useWorkspaceIdParam } from "../../components/AppShell";
import { api, ApiError, assetUrl, forceLogin, type ProfileUser } from "../../lib/api";
import { avatarColor, initials } from "../../lib/avatar";
import {
  hydrateNotifyPrefs,
  setNotifyPrefs,
  useNotifyPrefs,
  type MessageNotifyMode,
  type NotifyPrefs,
} from "../../lib/notify-prefs";
import { createPlatformAdapter } from "../../platform/adapter";
import { pushFlashToast } from "../../components/FlashToastHost";

const platform = createPlatformAdapter();

type IntegrationProvider = "google_calendar" | "outlook_calendar" | "zoom" | "openai";

interface IntegrationRow {
  provider: IntegrationProvider;
  connected: boolean;
  accountEmail?: string | null;
  accountLabel?: string | null;
  secretLast4?: string | null;
  configured: boolean;
}

const OAUTH_CARDS: Array<{ provider: Exclude<IntegrationProvider, "openai">; title: string; blurb: string }> = [
  {
    provider: "google_calendar",
    title: "Google Calendar",
    blurb: "Show your Google events alongside ConnectHUB on the calendar page.",
  },
  {
    provider: "outlook_calendar",
    title: "Outlook Calendar",
    blurb: "Overlay Microsoft 365 / Outlook events on your workspace calendar.",
  },
  {
    provider: "zoom",
    title: "Zoom",
    blurb: "Create a Zoom meeting link when you schedule an event.",
  },
];

interface DeviceSession {
  id: string;
  deviceLabel: string | null;
  ip: string | null;
  userAgent: string | null;
  createdAt: string;
  expiresAt: string;
}

interface PendingPhoto {
  name: string;
  url: string;
  width: number;
  height: number;
}

const MAX_PROFILE_PHOTO_BYTES = 10 * 1024 * 1024;
const PROFILE_PHOTO_TYPES = new Set(["image/jpeg", "image/png", "image/webp", "image/gif"]);

function errorText(error: unknown): string {
  if (error instanceof ApiError && error.body && typeof error.body === "object") {
    const message = (error.body as { message?: unknown }).message;
    if (typeof message === "string") return message;
    if (Array.isArray(message)) return message.join(", ");
  }
  return error instanceof Error ? error.message : String(error);
}

function SettingsView() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const workspaceId = useWorkspaceIdParam();
  const [sessions, setSessions] = useState<DeviceSession[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);
  const prefs = useNotifyPrefs(workspaceId);
  const [testResult, setTestResult] = useState<string | null>(null);
  const [profile, setProfile] = useState<ProfileUser | null>(null);
  const [photoBusy, setPhotoBusy] = useState(false);
  const [photoError, setPhotoError] = useState<string | null>(null);
  const [pendingPhoto, setPendingPhoto] = useState<PendingPhoto | null>(null);
  const [cropZoom, setCropZoom] = useState(1);
  const [cropX, setCropX] = useState(0);
  const [cropY, setCropY] = useState(0);
  const [confirmRemovePhoto, setConfirmRemovePhoto] = useState(false);
  const fileInputRef = useRef<HTMLInputElement | null>(null);
  const cropCanvasRef = useRef<HTMLCanvasElement | null>(null);
  const cropImageRef = useRef<HTMLImageElement | null>(null);
  const [integrations, setIntegrations] = useState<IntegrationRow[]>([]);
  const [integrationBusy, setIntegrationBusy] = useState<string | null>(null);
  const [integrationNote, setIntegrationNote] = useState<string | null>(null);
  const [openAiKey, setOpenAiKey] = useState("");

  async function sendTestNotification() {
    setTestResult(null);
    try {
      if (prefs.inAppFlash) {
        pushFlashToast({
          title: "connectHUB",
          body: "In-app flash notifications are working.",
          route: `/settings?workspaceId=${workspaceId ?? ""}`,
          tag: "test-flash",
        });
      }
      await platform.notifications.notify("connectHUB", "Notifications are working on this device.", {
        tag: "test",
        route: `/settings?workspaceId=${workspaceId ?? ""}`,
        group: "test",
      });
      // The adapter cannot report what the OS did with it, so say what was
      // attempted rather than claiming success.
      setTestResult(
        prefs.inAppFlash
          ? "Sent an in-app flash and an OS notification. If nothing appeared on the desktop, notifications may be blocked for this app at the OS level."
          : "Sent an OS notification. If nothing appeared, notifications may be blocked for this app at the OS level.",
      );
    } catch (err) {
      setTestResult(`Could not send: ${err instanceof Error ? err.message : String(err)}`);
    }
  }

  async function loadDevices() {
    try {
      const res = await api.listDevices();
      setSessions(res.sessions);
    } catch (err) {
      if (err instanceof ApiError && err.status === 401) void forceLogin(router);
      else setError(err instanceof ApiError ? JSON.stringify(err.body) : String(err));
    }
  }

  async function loadProfile() {
    try {
      const result = await api.profile();
      setProfile(result.user);
    } catch (err) {
      if (err instanceof ApiError && err.status === 401) void forceLogin(router);
      else setPhotoError(errorText(err));
    }
  }

  async function loadIntegrations() {
    try {
      const res = await api.listIntegrations();
      setIntegrations(res.integrations as IntegrationRow[]);
    } catch (err) {
      if (err instanceof ApiError && err.status === 401) void forceLogin(router);
    }
  }

  useEffect(() => {
    void loadDevices();
    void loadProfile();
    void loadIntegrations();
  }, []);

  useEffect(() => {
    const status = searchParams.get("integration");
    if (!status) return;
    const message = searchParams.get("integrationMessage");
    if (status === "connected") setIntegrationNote("Account connected.");
    else setIntegrationNote(message || "Could not connect that account.");
    void loadIntegrations();
  }, [searchParams]);

  useEffect(() => {
    if (!workspaceId) return;
    void api.notificationPreferences(workspaceId)
      .then(async ({ preferences }) => preferences.updatedAt == null
        ? (await api.updateNotificationPreferences(workspaceId, prefs)).preferences
        : preferences)
      .then((preferences) => hydrateNotifyPrefs(workspaceId, preferences))
      .catch((err) => setError(errorText(err)));
  }, [workspaceId]);

  function saveNotificationPreference(patch: Partial<NotifyPrefs>) {
    if (!workspaceId) return;
    const previous = prefs;
    setNotifyPrefs(workspaceId, patch);
    void api.updateNotificationPreferences(workspaceId, patch)
      .then(({ preferences }) => hydrateNotifyPrefs(workspaceId, preferences))
      .catch((err) => {
        hydrateNotifyPrefs(workspaceId, previous);
        setError(errorText(err));
      });
  }

  useEffect(() => {
    const canvas = cropCanvasRef.current;
    const image = cropImageRef.current;
    if (!canvas || !image || !pendingPhoto) return;
    const context = canvas.getContext("2d");
    if (!context) return;

    const cropSize = Math.min(pendingPhoto.width, pendingPhoto.height) / cropZoom;
    const maxOffsetX = Math.max(0, (pendingPhoto.width - cropSize) / 2);
    const maxOffsetY = Math.max(0, (pendingPhoto.height - cropSize) / 2);
    const sourceX = pendingPhoto.width / 2 + cropX * maxOffsetX - cropSize / 2;
    const sourceY = pendingPhoto.height / 2 + cropY * maxOffsetY - cropSize / 2;
    context.clearRect(0, 0, 512, 512);
    context.drawImage(image, sourceX, sourceY, cropSize, cropSize, 0, 0, 512, 512);
  }, [cropX, cropY, cropZoom, pendingPhoto]);

  function closePhotoEditor() {
    if (pendingPhoto) URL.revokeObjectURL(pendingPhoto.url);
    cropImageRef.current = null;
    setPendingPhoto(null);
    setCropZoom(1);
    setCropX(0);
    setCropY(0);
  }

  function pickPhoto(file: File | undefined) {
    setPhotoError(null);
    if (!file) return;
    if (!PROFILE_PHOTO_TYPES.has(file.type)) {
      setPhotoError("Choose a JPEG, PNG, WebP, or GIF image.");
      return;
    }
    if (file.size > MAX_PROFILE_PHOTO_BYTES) {
      setPhotoError("Profile photo must be 10 MB or smaller.");
      return;
    }

    const url = URL.createObjectURL(file);
    const image = new Image();
    image.onload = () => {
      if (image.naturalWidth < 512 || image.naturalHeight < 512) {
        URL.revokeObjectURL(url);
        setPhotoError("Profile photo must be at least 512 by 512 pixels.");
        return;
      }
      if (pendingPhoto) URL.revokeObjectURL(pendingPhoto.url);
      cropImageRef.current = image;
      setCropZoom(1);
      setCropX(0);
      setCropY(0);
      setPendingPhoto({ name: file.name, url, width: image.naturalWidth, height: image.naturalHeight });
    };
    image.onerror = () => {
      URL.revokeObjectURL(url);
      setPhotoError("This image could not be opened.");
    };
    image.src = url;
  }

  async function savePhoto() {
    const canvas = cropCanvasRef.current;
    if (!canvas || !pendingPhoto) return;
    setPhotoBusy(true);
    setPhotoError(null);
    try {
      const blob = await new Promise<Blob>((resolve, reject) => {
        canvas.toBlob((value) => (value ? resolve(value) : reject(new Error("Could not crop photo"))), "image/webp", 0.9);
      });
      const upload = new File([blob], `${pendingPhoto.name.replace(/\.[^.]+$/, "") || "profile"}.webp`, { type: "image/webp" });
      const result = await api.uploadProfilePhoto(upload);
      setProfile(result.user);
      window.dispatchEvent(new CustomEvent("profile:updated", { detail: result.user }));
      closePhotoEditor();
    } catch (err) {
      setPhotoError(errorText(err));
    } finally {
      setPhotoBusy(false);
    }
  }

  async function removePhoto() {
    setPhotoBusy(true);
    setPhotoError(null);
    try {
      const result = await api.removeProfilePhoto();
      setProfile(result.user);
      setConfirmRemovePhoto(false);
      window.dispatchEvent(new CustomEvent("profile:updated", { detail: result.user }));
    } catch (err) {
      setPhotoError(errorText(err));
    } finally {
      setPhotoBusy(false);
    }
  }

  async function revoke(sessionId: string) {
    setBusy(true);
    setError(null);
    try {
      await api.revokeDevice(sessionId);
      await loadDevices();
    } catch (err) {
      setError(err instanceof ApiError ? JSON.stringify(err.body) : String(err));
    } finally {
      setBusy(false);
    }
  }

  async function logout() {
    await api.logout();
    router.push("/login");
  }

  async function connectOAuth(provider: "google_calendar" | "outlook_calendar" | "zoom") {
    setIntegrationBusy(provider);
    setIntegrationNote(null);
    try {
      const returnTo = workspaceId ? `/settings?workspaceId=${workspaceId}` : "/settings";
      const { url } = await api.startIntegrationOAuth(provider, returnTo);
      window.location.href = url;
    } catch (err) {
      setIntegrationNote(errorText(err));
      setIntegrationBusy(null);
    }
  }

  async function disconnect(provider: IntegrationProvider) {
    setIntegrationBusy(provider);
    setIntegrationNote(null);
    try {
      await api.disconnectIntegration(provider);
      await loadIntegrations();
      setIntegrationNote("Disconnected.");
    } catch (err) {
      setIntegrationNote(errorText(err));
    } finally {
      setIntegrationBusy(null);
    }
  }

  async function saveOpenAi() {
    if (!openAiKey.trim()) return;
    setIntegrationBusy("openai");
    setIntegrationNote(null);
    try {
      await api.saveOpenAiKey(openAiKey.trim());
      setOpenAiKey("");
      await loadIntegrations();
      setIntegrationNote("OpenAI key saved.");
    } catch (err) {
      setIntegrationNote(errorText(err));
    } finally {
      setIntegrationBusy(null);
    }
  }

  const openai = integrations.find((row) => row.provider === "openai");

  return (
    <AppShell active="home" title="Settings" subtitle="Account and devices">
      <ScreenHeader
        title="Settings"
        subtitle="Manage sessions and workspace shortcuts"
        actions={
          <button className="screen-btn" type="button" onClick={logout}>
            Sign out
          </button>
        }
      />
      <div className="screen-body">
        {error && <p className="error-text">{error}</p>}
        {photoError && <p className="error-text">{photoError}</p>}
        <div className="screen-grid">
          <section className="screen-card profile-settings-card">
            <div className="profile-settings-copy">
              <h2>Profile photo</h2>
              <p>Use a clear square photo so teammates can recognize you.</p>
              {profile && (
                <div className="profile-settings-identity">
                  <strong>{profile.name}</strong>
                  <span>@{profile.username}</span>
                </div>
              )}
              <p className="profile-photo-help">JPEG, PNG, WebP or GIF · minimum 512×512 · saved at 512×512</p>
            </div>
            <div className="profile-photo-actions">
              <span
                className="profile-photo-preview"
                style={profile ? { background: avatarColor(String(profile.id)).bg, color: avatarColor(String(profile.id)).fg } : undefined}
              >
                {profile?.avatarUrl ? (
                  <img src={assetUrl(profile.avatarUrl) ?? undefined} alt={`${profile.name} profile`} />
                ) : (
                  initials(profile?.name)
                )}
              </span>
              <input
                ref={fileInputRef}
                type="file"
                accept="image/jpeg,image/png,image/webp,image/gif,.jpg,.jpeg,.png,.webp,.gif"
                className="profile-photo-input"
                onChange={(event) => {
                  pickPhoto(event.target.files?.[0]);
                  event.currentTarget.value = "";
                }}
              />
              <button className="screen-btn primary" type="button" onClick={() => fileInputRef.current?.click()} disabled={photoBusy}>
                {profile?.avatarUrl ? "Change photo" : "Upload photo"}
              </button>
              {profile?.avatarUrl && (
                <button className="profile-photo-remove" type="button" onClick={() => setConfirmRemovePhoto(true)} disabled={photoBusy}>
                  Remove photo
                </button>
              )}
            </div>
          </section>

          <section className="screen-card">
            <h2>Workspace</h2>
            <div className="screen-actions" style={{ marginTop: 8 }}>
              <Link className="screen-btn" href={`/home?workspaceId=${workspaceId}`}>Home</Link>
              <Link className="screen-btn" href={`/admin?workspaceId=${workspaceId}`}>Admin</Link>
              <Link className="screen-btn" href="/workspaces">Switch workspace</Link>
            </div>
          </section>

          <section className="screen-card">
            <h2>Connected calendars & Zoom</h2>
            <p>Optional. Connect your own accounts — events stay private to you until you share them here.</p>
            {integrationNote && <p className="integration-note">{integrationNote}</p>}
            <div className="integration-list">
              {OAUTH_CARDS.map((card) => {
                const row = integrations.find((item) => item.provider === card.provider);
                const connected = Boolean(row?.connected);
                return (
                  <div className="integration-row" key={card.provider}>
                    <div>
                      <strong>{card.title}</strong>
                      <p>{card.blurb}</p>
                      {connected && (
                        <small>
                          Connected{row?.accountEmail ? ` as ${row.accountEmail}` : row?.accountLabel ? ` · ${row.accountLabel}` : ""}
                        </small>
                      )}
                      {!connected && row && !row.configured && (
                        <small className="integration-warn">Server OAuth credentials are not configured yet.</small>
                      )}
                    </div>
                    <div className="integration-actions">
                      {connected ? (
                        <button
                          className="screen-btn"
                          type="button"
                          disabled={integrationBusy === card.provider}
                          onClick={() => void disconnect(card.provider)}
                        >
                          Disconnect
                        </button>
                      ) : (
                        <button
                          className="screen-btn primary"
                          type="button"
                          disabled={integrationBusy === card.provider || row?.configured === false}
                          onClick={() => void connectOAuth(card.provider)}
                        >
                          Connect
                        </button>
                      )}
                    </div>
                  </div>
                );
              })}
            </div>
          </section>

          <section className="screen-card">
            <h2>Writing assistant</h2>
            <p>
              Rewrite messages in the composer. Uses your OpenAI key (stored encrypted), or a server
              <code> OPENAI_API_KEY</code> if you leave this blank.
            </p>
            {openai?.connected && (
              <p className="integration-note">Key on file ending in ···{openai.secretLast4}</p>
            )}
            <div className="integration-openai-row">
              <input
                type="password"
                value={openAiKey}
                onChange={(e) => setOpenAiKey(e.target.value)}
                placeholder="sk-…"
                autoComplete="off"
                aria-label="OpenAI API key"
              />
              <button className="screen-btn primary" type="button" disabled={integrationBusy === "openai" || !openAiKey.trim()} onClick={() => void saveOpenAi()}>
                Save key
              </button>
              {openai?.connected && (
                <button className="screen-btn" type="button" disabled={integrationBusy === "openai"} onClick={() => void disconnect("openai")}>
                  Remove
                </button>
              )}
            </div>
          </section>

          <section className="screen-card">
            <h2>Notifications</h2>
            <p>Desktop alerts, sound, and in-app flash toasts for this workspace.</p>

            <label className="notif-field">
              <span>Messages</span>
              <select
                value={prefs.messages}
                onChange={(e) => saveNotificationPreference({ messages: e.target.value as MessageNotifyMode })}
              >
                <option value="all">Every message</option>
                <option value="mentions">Only DMs and @mentions</option>
                <option value="off">Never</option>
              </select>
            </label>

            {(
              [
                ["calls", "Incoming and missed calls"],
                ["tasks", "Task assignments and status changes"],
                ["calendar", "Event invitations and changes"],
                ["sound", "Play a sound"],
                ["inAppFlash", "Show flash notifications in the app (right side)"],
              ] as const
            ).map(([key, label]) => (
              <label className="notif-toggle" key={key}>
                <input type="checkbox" checked={prefs[key]} onChange={(e) => saveNotificationPreference({ [key]: e.target.checked })} />
                <span>{label}</span>
              </label>
            ))}

            <div className="screen-actions" style={{ marginTop: 12 }}>
              {/* The only honest check that the OS actually granted permission —
                  a toggle being on says nothing about what the OS will show. */}
              <button className="screen-btn" type="button" onClick={sendTestNotification}>
                Send a test notification
              </button>
            </div>
            {testResult && <p className="muted" style={{ marginTop: 8 }}>{testResult}</p>}
          </section>

          <section className="screen-card wide">
            <h2>Signed-in devices</h2>
            <p>Revoke a session to force that device to sign in again.</p>
            <div className="admin-table" style={{ marginTop: 12 }}>
              {sessions.map((session) => (
                <div className="admin-row" key={session.id}>
                  <div className="admin-row-main">
                    <strong>{session.deviceLabel ?? "Browser session"}</strong>
                    <span>{session.userAgent ?? "Unknown agent"}</span>
                    <span className="muted">
                      {session.ip ?? "no ip"} · created {new Date(session.createdAt).toLocaleString()}
                    </span>
                  </div>
                  <button className="screen-btn" type="button" disabled={busy} onClick={() => revoke(session.id)}>
                    Revoke
                  </button>
                </div>
              ))}
              {sessions.length === 0 && <div className="empty-state">No active sessions returned.</div>}
            </div>
          </section>
        </div>
      </div>

      {pendingPhoto && (
        <div className="profile-photo-backdrop" role="presentation" onMouseDown={(event) => event.target === event.currentTarget && closePhotoEditor()}>
          <section className="profile-photo-modal" role="dialog" aria-modal="true" aria-labelledby="profile-photo-title">
            <div className="profile-photo-modal-head">
              <div>
                <h2 id="profile-photo-title">Adjust your photo</h2>
                <p>Move the sliders until your face is framed clearly.</p>
              </div>
              <button type="button" aria-label="Close photo editor" onClick={closePhotoEditor}>×</button>
            </div>
            <div className="profile-crop-stage">
              <canvas ref={cropCanvasRef} width={512} height={512} aria-label="Profile photo crop preview" />
            </div>
            <div className="profile-crop-controls">
              <label>
                <span>Zoom</span>
                <input type="range" min="1" max="3" step="0.01" value={cropZoom} onChange={(event) => setCropZoom(Number(event.target.value))} />
              </label>
              <label>
                <span>Horizontal</span>
                <input type="range" min="-1" max="1" step="0.01" value={cropX} onChange={(event) => setCropX(Number(event.target.value))} />
              </label>
              <label>
                <span>Vertical</span>
                <input type="range" min="-1" max="1" step="0.01" value={cropY} onChange={(event) => setCropY(Number(event.target.value))} />
              </label>
            </div>
            <div className="profile-photo-modal-actions">
              <button className="screen-btn" type="button" onClick={closePhotoEditor} disabled={photoBusy}>Cancel</button>
              <button className="screen-btn primary" type="button" onClick={() => void savePhoto()} disabled={photoBusy}>
                {photoBusy ? "Saving…" : "Save photo"}
              </button>
            </div>
          </section>
        </div>
      )}

      {confirmRemovePhoto && (
        <div className="profile-photo-backdrop" role="presentation" onMouseDown={(event) => event.target === event.currentTarget && setConfirmRemovePhoto(false)}>
          <section className="profile-photo-modal confirm" role="alertdialog" aria-modal="true" aria-labelledby="remove-photo-title">
            <div className="profile-photo-modal-head">
              <div>
                <h2 id="remove-photo-title">Remove profile photo?</h2>
                <p>Your initials will be shown until you upload another photo.</p>
              </div>
            </div>
            <div className="profile-photo-modal-actions">
              <button className="screen-btn" type="button" onClick={() => setConfirmRemovePhoto(false)} disabled={photoBusy}>Cancel</button>
              <button className="screen-btn danger" type="button" onClick={() => void removePhoto()} disabled={photoBusy}>
                {photoBusy ? "Removing…" : "Yes, remove photo"}
              </button>
            </div>
          </section>
        </div>
      )}
    </AppShell>
  );
}

export default function SettingsPage() {
  return (
    <Suspense fallback={<main className="page"><div className="empty-state">Loading settings…</div></main>}>
      <SettingsView />
    </Suspense>
  );
}
