import { useEffect, useMemo, useState } from "react";
import type { Client } from "../entities/client/types";
import type { Invite } from "../entities/invite/types";
import { syncServiceLaunchLink } from "../entities/service/links";
import type { LauncherServiceView, Service } from "../entities/service/types";
import type { ClientGroup, ClientMembership, LauncherUser } from "../entities/user/types";
import {
  createAdminClient,
  createAdminGroup,
  createAdminInvite,
  createAdminService,
  createAdminUser,
  deleteAdminClient,
  deleteAdminGroup,
  deleteAdminInvite,
  deleteAdminMembership,
  deleteAdminService,
  fetchControlPlaneSnapshot,
  reorderAdminServices,
  retryAdminSync,
  setAdminUserServiceAccess,
  updateAdminClient,
  updateAdminGroup,
  updateAdminInvite,
  updateAdminMembership,
  updateAdminService,
  updateAdminUserProfile,
  type ControlPlaneMutationResult,
} from "../shared/api/adminApi";
import {
  buildLauncherServices,
  buildMe,
  initialLauncherData,
  profileOptions,
  type LauncherData,
} from "../shared/api/mockApi";
import {
  fetchAuthSession,
  fetchAvailableApps,
  type AuthenticatedSession,
  type AuthSession,
  type LauncherAuthApp,
} from "../shared/api/authApi";
import { updateOwnPassword, updateOwnProfile } from "../shared/api/profileApi";
import { subscribeToNodeDCLogoutEvents } from "../shared/session/sessionSync";
import { loadPersistedLauncherData } from "../shared/api/storageApi";
import {
  AdminOverlay,
  type AccessAssignmentValue,
  type CreateUserCommand,
  type SetUserServiceAccessCommand,
} from "../widgets/admin-overlay/AdminOverlay";
import { ProfileSettingsPanel } from "../widgets/profile-settings-panel/ProfileSettingsPanel";
import { ServiceRail } from "../widgets/service-rail/ServiceRail";
import { ServiceStage } from "../widgets/service-stage/ServiceStage";
import { TopBar } from "../widgets/top-bar/TopBar";

export function LauncherApp() {
  const [data, setData] = useState<LauncherData>(() => syncLauncherServiceLinks(initialLauncherData));
  const [activeProfileId, setActiveProfileId] = useState(profileOptions[0].userId);
  const [activeClientId, setActiveClientId] = useState(profileOptions[0].defaultClientId);
  const [selectedServiceId, setSelectedServiceId] = useState<string | undefined>();
  const [adminOpen, setAdminOpen] = useState(false);
  const [authSession, setAuthSession] = useState<AuthSession | null>(null);
  const [authApps, setAuthApps] = useState<LauncherAuthApp[] | null>(null);
  const [profileSettingsOpen, setProfileSettingsOpen] = useState(false);
  const [pendingAccessAssignments, setPendingAccessAssignments] = useState<Record<string, AccessAssignmentValue>>({});

  const me = useMemo(() => buildMe(data, activeProfileId, activeClientId), [data, activeProfileId, activeClientId]);
  const activeProfileUser = data.users.find((user) => user.id === activeProfileId) ?? data.users[0];
  const runtimeMe = useMemo(() => {
    if (!authSession?.authenticated) return me;

    return {
      ...me,
      user: {
        ...me.user,
        authentikUserId: authSession.user.sub,
        email: me.user.email || authSession.user.email,
        name: me.user.name || authSession.user.name,
        avatarUrl: me.user.avatarUrl ?? authSession.user.avatarUrl,
      },
      mockAuthentikClaims: {
        ...me.mockAuthentikClaims,
        sub: authSession.user.sub,
        email: authSession.user.email || me.mockAuthentikClaims.email,
        name: authSession.user.name || me.mockAuthentikClaims.name,
        avatarUrl: authSession.user.avatarUrl ?? null,
        groups: authSession.groups,
      },
    };
  }, [authSession, me]);
  const resolvedClientId = me.activeClientId;
  const authAppsBySlug = useMemo(() => new Map((authApps ?? []).map((app) => [app.slug, app])), [authApps]);
  const launcherServices = useMemo(
    () => {
      const services = buildLauncherServices(data, activeProfileId, resolvedClientId);

      if (!authSession?.authenticated || authApps === null) {
        return services;
      }

      return services.map((service) => {
        const app = authAppsBySlug.get(service.slug);

        if (!app) {
          return {
            ...service,
            userAccess: "denied" as const,
            openUrl: null,
            effectiveAccess: {
              ...service.effectiveAccess,
              allowed: false,
              visible: true,
              openEnabled: false,
              reason: "Нет доступа",
            },
          };
        }

        const openEnabled = app.hasAccess && app.status === "active";

        return {
          ...service,
          title: app.title || service.title,
          description: app.description || service.description,
          openUrl: openEnabled ? app.openUrl || app.url || service.openUrl : null,
          userAccess: openEnabled ? ("allowed" as const) : ("denied" as const),
          effectiveAccess: {
            ...service.effectiveAccess,
            allowed: app.hasAccess,
            visible: true,
            openEnabled,
            reason: app.accessReason || (app.hasAccess ? "Доступ подтверждён" : "Нет доступа"),
          },
        };
      });
    },
    [authApps, authAppsBySlug, authSession, data, activeProfileId, resolvedClientId]
  );

  useEffect(() => {
    if (!launcherServices.length) {
      setSelectedServiceId(undefined);
      return;
    }

    if (selectedServiceId && !launcherServices.some((service) => service.id === selectedServiceId)) {
      setSelectedServiceId(undefined);
    }
  }, [launcherServices, selectedServiceId]);

  const selectedService = launcherServices.find((service) => service.id === selectedServiceId);

  useEffect(() => {
    let isMounted = true;

    fetchAuthSession()
      .then(async (session) => {
        if (!isMounted) return;

        setAuthSession(session);

        if (!session.authenticated) {
          setAuthApps([]);
          return;
        }

        const apps = await fetchAvailableApps();

        if (isMounted) {
          setAuthApps(apps);
        }
      })
      .catch((error: unknown) => {
        if (!isMounted) return;

        setAuthSession({ authenticated: false, loginUrl: "/auth/login" });
        setAuthApps([]);
        console.warn(error instanceof Error ? error.message : "Не удалось проверить сессию платформы");
      });

    return () => {
      isMounted = false;
    };
  }, []);

  useEffect(() => {
    if (!authSession || authSession.authenticated) return;

    window.location.replace(buildLoginRedirectUrl(authSession.loginUrl));
  }, [authSession]);

  useEffect(() => {
    let isRedirecting = false;

    return subscribeToNodeDCLogoutEvents(() => {
      if (isRedirecting) return;

      isRedirecting = true;
      window.location.replace(buildLoginRedirectUrl("/auth/login?prompt=login"));
    });
  }, []);

  useEffect(() => {
    let isMounted = true;

    const validateRestoredSession = (event: PageTransitionEvent) => {
      if (!event.persisted) return;

      fetchAuthSession()
        .then((session) => {
          if (!isMounted) return;

          if (!session.authenticated) {
            window.location.replace(buildLoginRedirectUrl(session.loginUrl));
            return;
          }

          setAuthSession(session);
        })
        .catch(() => {
          if (isMounted) {
            window.location.replace(buildLoginRedirectUrl("/auth/login"));
          }
        });
    };

    window.addEventListener("pageshow", validateRestoredSession);

    return () => {
      isMounted = false;
      window.removeEventListener("pageshow", validateRestoredSession);
    };
  }, []);

  useEffect(() => {
    if (!authSession?.authenticated) return;

    const nextContext = resolveAuthenticatedContext(data, authSession, activeProfileId, activeClientId);

    if (activeProfileId !== nextContext.profileId) {
      setActiveProfileId(nextContext.profileId);
    }

    if (activeClientId !== nextContext.clientId) {
      setActiveClientId(nextContext.clientId);
    }
  }, [activeClientId, activeProfileId, authSession, data]);

  useEffect(() => {
    let isMounted = true;

    loadPersistedLauncherData()
      .then((persistedData) => {
        if (isMounted && persistedData) {
          setData(syncLauncherServiceLinks(persistedData));
        }
      });

    return () => {
      isMounted = false;
    };
  }, []);

  useEffect(() => {
    if (!authSession?.authenticated || !canUseAdminApi(authSession)) return;

    let isMounted = true;

    fetchControlPlaneSnapshot()
      .then((snapshot) => {
        if (isMounted) {
          setData(syncLauncherServiceLinks(snapshot.data));
        }
      })
      .catch((error: unknown) => {
        console.warn(error instanceof Error ? error.message : "Не удалось загрузить control-plane snapshot");
      });

    return () => {
      isMounted = false;
    };
  }, [authSession]);

  useEffect(() => {
    if (!authSession?.authenticated) return;

    let isMounted = true;

    const refreshRuntimeState = async () => {
      try {
        const nextSession = await fetchAuthSession();

        if (!isMounted) return;

        setAuthSession(nextSession);

        if (!nextSession.authenticated) {
          setAuthApps([]);
          return;
        }

        const [persistedData, apps] = await Promise.all([
          canUseAdminApi(nextSession)
            ? fetchControlPlaneSnapshot().then((snapshot) => snapshot.data)
            : loadPersistedLauncherData(),
          fetchAvailableApps(),
        ]);

        if (!isMounted) return;

        if (persistedData) {
          setData(syncLauncherServiceLinks(persistedData));
        }

        setAuthApps(apps);
      } catch (error: unknown) {
        console.warn(error instanceof Error ? error.message : "Не удалось обновить runtime состояние Launcher");
      }
    };

    const eventSource = new EventSource("/api/events");

    eventSource.addEventListener("nodedc-runtime", () => {
      void refreshRuntimeState();
    });

    eventSource.onerror = () => {
      console.warn("Launcher event stream disconnected; browser will retry automatically");
    };

    return () => {
      isMounted = false;
      eventSource.close();
    };
  }, [authSession?.authenticated]);

  function handleProfileChange(userId: string) {
    const profile = profileOptions.find((option) => option.userId === userId);
    setActiveProfileId(userId);
    setActiveClientId(profile?.defaultClientId ?? activeClientId);
    setAdminOpen(false);
  }

  function handleLaunch(service: LauncherServiceView) {
    if (!service.openUrl || !service.effectiveAccess.openEnabled) return;
    window.open(service.openUrl, "_blank", "noopener,noreferrer");
  }

  function handleServiceSelect(serviceId: string) {
    setSelectedServiceId((current) => (current === serviceId ? undefined : serviceId));
  }

  function handleStageStep(direction: "previous" | "next") {
    if (!launcherServices.length) return;

    setSelectedServiceId((current) => {
      const currentIndex = current ? launcherServices.findIndex((service) => service.id === current) : -1;
      const fallbackIndex = direction === "next" ? 0 : launcherServices.length - 1;
      const nextIndex =
        currentIndex === -1
          ? fallbackIndex
          : direction === "next"
            ? (currentIndex + 1) % launcherServices.length
            : (currentIndex - 1 + launcherServices.length) % launcherServices.length;

      return launcherServices[nextIndex]?.id;
    });
  }

  function applyControlPlaneMutation(request: Promise<ControlPlaneMutationResult>) {
    request
      .then((result) => {
        setData(syncLauncherServiceLinks(result.data));
      })
      .catch((error: unknown) => {
        console.warn(error instanceof Error ? error.message : "Не удалось выполнить admin API операцию");
      });
  }

  function handleSetUserServiceAccess({ userId, serviceId, value }: SetUserServiceAccessCommand) {
    const assignmentKey = accessAssignmentKey(userId, serviceId);

    if (pendingAccessAssignments[assignmentKey]) {
      return;
    }

    setPendingAccessAssignments((current) => ({ ...current, [assignmentKey]: value }));
    setAdminUserServiceAccess({ userId, serviceId, value })
      .then((result) => {
        setData(syncLauncherServiceLinks(result.data));
      })
      .catch((error: unknown) => {
        console.warn(error instanceof Error ? error.message : "Не удалось выполнить admin API операцию");
      })
      .finally(() => {
        setPendingAccessAssignments((current) => {
          const { [assignmentKey]: _completed, ...rest } = current;
          return rest;
        });
      });
  }

  function handleCreateInvite(invite: Pick<Invite, "clientId" | "email" | "role">) {
    applyControlPlaneMutation(createAdminInvite(invite));
  }

  function handleUpdateInvite(inviteId: string, patch: Partial<Invite>) {
    applyControlPlaneMutation(updateAdminInvite(inviteId, patch));
  }

  function handleDeleteInvite(inviteId: string) {
    applyControlPlaneMutation(deleteAdminInvite(inviteId));
  }

  function handleRetrySync(syncId: string) {
    applyControlPlaneMutation(retryAdminSync(syncId));
  }

  function handleUpdateService(serviceId: string, patch: Partial<Service>) {
    applyControlPlaneMutation(updateAdminService(serviceId, patch));
  }

  function handleCreateClient() {
    const index = data.clients.length + 1;

    applyControlPlaneMutation(
      createAdminClient({
        type: "company",
        name: `Новый клиент ${index}`,
        legalName: `Новый клиент ${index}`,
        status: "demo",
        demoEndsAt: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(),
        contactName: "",
        contactEmail: "",
        notes: "",
      })
    );
  }

  function handleUpdateClient(clientId: string, patch: Partial<Client>) {
    applyControlPlaneMutation(updateAdminClient(clientId, patch));
  }

  function handleDeleteClient(clientId: string) {
    const nextClientId = data.clients.find((client) => client.id !== clientId)?.id ?? activeClientId;

    applyControlPlaneMutation(deleteAdminClient(clientId));

    if (activeClientId === clientId) {
      setActiveClientId(nextClientId);
    }
  }

  function handleUpdateUser(userId: string, patch: Partial<LauncherUser>) {
    applyControlPlaneMutation(updateAdminUserProfile(userId, patch));
  }

  async function handleUpdateOwnProfile(patch: Partial<LauncherUser>) {
    const result = await updateOwnProfile(patch);
    setData(syncLauncherServiceLinks(result.data));
  }

  async function handleUpdateOwnPassword(newPassword: string) {
    const result = await updateOwnPassword(newPassword);
    setData(syncLauncherServiceLinks(result.data));
  }

  function handleCreateUser(command: CreateUserCommand) {
    createAdminUser(command)
      .then((result) => {
        setData(syncLauncherServiceLinks(result.data));

        if (result.provisioning?.temporaryPassword) {
          window.alert(`Пользователь создан. Временный пароль: ${result.provisioning.temporaryPassword}`);
        }
      })
      .catch((error: unknown) => {
        console.warn(error instanceof Error ? error.message : "Не удалось создать пользователя");
      });
  }

  function handleUpdateMembership(membershipId: string, patch: Partial<ClientMembership>) {
    applyControlPlaneMutation(updateAdminMembership(membershipId, patch));
  }

  function handleDeleteMembership(membershipId: string) {
    applyControlPlaneMutation(deleteAdminMembership(membershipId));
  }

  function handleCreateGroup(clientId: string) {
    applyControlPlaneMutation(createAdminGroup({ clientId, name: "Новая группа", description: "Описание группы", memberIds: [] }));
  }

  function handleUpdateGroup(groupId: string, patch: Partial<ClientGroup>) {
    applyControlPlaneMutation(updateAdminGroup(groupId, patch));
  }

  function handleDeleteGroup(groupId: string) {
    applyControlPlaneMutation(deleteAdminGroup(groupId));
  }

  function handleReorderServices(orderedServiceIds: string[]) {
    applyControlPlaneMutation(reorderAdminServices(orderedServiceIds));
  }

  function handleCreateService() {
    applyControlPlaneMutation(createAdminService());
  }

  function handleDeleteService(serviceId: string) {
    applyControlPlaneMutation(deleteAdminService(serviceId));

    setSelectedServiceId((current) => (current === serviceId ? undefined : current));
  }

  if (!authSession) {
    return <AuthStateScreen title="Проверяем сессию NODE.DC" description="Платформа подготавливает рабочую область и список приложений." />;
  }

  if (!authSession.authenticated) {
    return null;
  }

  const handleLogout = () => {
    window.location.replace(authSession.logoutUrl);
  };

  return (
    <div className="launcher-app">
      <TopBar
        me={runtimeMe}
        clients={data.clients}
        profileOptions={profileOptions}
        activeProfileId={activeProfileId}
        activeClientId={resolvedClientId}
        adminOpen={adminOpen}
        onProfileChange={handleProfileChange}
        onClientChange={setActiveClientId}
        onToggleAdmin={() => setAdminOpen((current) => !current)}
        onOpenShowcase={() => setAdminOpen(false)}
        onOpenProfileSettings={() => setProfileSettingsOpen(true)}
        onLogout={handleLogout}
      />

      <main className="launcher-main">
        <ServiceStage
          service={selectedService}
          hasServices={launcherServices.length > 0}
          onLaunch={handleLaunch}
          onSelectPrevious={() => handleStageStep("previous")}
          onSelectNext={() => handleStageStep("next")}
        />
        {adminOpen && me.permissions.canOpenAdmin ? (
          <AdminOverlay
            data={data}
            me={runtimeMe}
            activeClientId={resolvedClientId}
            onClose={() => setAdminOpen(false)}
            onSetUserServiceAccess={handleSetUserServiceAccess}
            onCreateInvite={handleCreateInvite}
            onUpdateInvite={handleUpdateInvite}
            onDeleteInvite={handleDeleteInvite}
            onRetrySync={handleRetrySync}
            onCreateClient={handleCreateClient}
            onUpdateClient={handleUpdateClient}
            onDeleteClient={handleDeleteClient}
            onCreateUser={handleCreateUser}
            onUpdateUser={handleUpdateUser}
            onUpdateMembership={handleUpdateMembership}
            onDeleteMembership={handleDeleteMembership}
            pendingAccessAssignments={pendingAccessAssignments}
            onCreateGroup={handleCreateGroup}
            onUpdateGroup={handleUpdateGroup}
            onDeleteGroup={handleDeleteGroup}
            onUpdateService={handleUpdateService}
            onReorderServices={handleReorderServices}
            onCreateService={handleCreateService}
            onDeleteService={handleDeleteService}
          />
        ) : null}
        {profileSettingsOpen && activeProfileUser ? (
          <ProfileSettingsPanel
            user={activeProfileUser}
            onClose={() => setProfileSettingsOpen(false)}
            onSaveProfile={handleUpdateOwnProfile}
            onChangePassword={handleUpdateOwnPassword}
          />
        ) : null}
        <ServiceRail services={launcherServices} selectedServiceId={selectedServiceId} onSelect={handleServiceSelect} />
      </main>
    </div>
  );
}

function syncLauncherServiceLinks(data: LauncherData): LauncherData {
  return {
    ...data,
    services: data.services.map(syncServiceLaunchLink),
  };
}

function accessAssignmentKey(userId: string, serviceId: string) {
  return `${userId}:${serviceId}`;
}

function canUseAdminApi(session: AuthSession): boolean {
  return (
    session.authenticated &&
    (session.isSuperAdmin || session.groups.includes("nodedc:launcher:admin") || session.groups.includes("nodedc:superadmin"))
  );
}

function resolveAuthenticatedContext(
  data: LauncherData,
  session: AuthenticatedSession,
  currentProfileId: string,
  currentClientId: string
): { profileId: string; clientId: string } {
  const sessionEmail = session.user.email?.toLowerCase();
  const sessionSub = session.user.sub;
  const profile =
    data.users.find(
      (user) =>
        (sessionSub && user.authentikUserId === sessionSub) ||
        (sessionEmail && user.email.toLowerCase() === sessionEmail)
    ) ??
    (session.isSuperAdmin ? data.users.find((user) => user.id === "user_root") : undefined) ??
    data.users.find((user) => user.id === currentProfileId) ??
    data.users[0];

  if (!profile) {
    return { profileId: currentProfileId, clientId: currentClientId };
  }

  return {
    profileId: profile.id,
    clientId: resolveDefaultClientId(data, profile.id, currentClientId),
  };
}

function resolveDefaultClientId(data: LauncherData, userId: string, requestedClientId: string): string {
  const user = data.users.find((item) => item.id === userId);
  const isRoot = user?.id === "user_root";
  const availableClientIds = isRoot
    ? data.clients.map((client) => client.id)
    : data.memberships.filter((membership) => membership.userId === userId && membership.status === "active").map((membership) => membership.clientId);

  if (requestedClientId && availableClientIds.includes(requestedClientId)) {
    return requestedClientId;
  }

  const defaultClientId = profileOptions.find((profile) => profile.userId === userId)?.defaultClientId;

  if (defaultClientId && availableClientIds.includes(defaultClientId)) {
    return defaultClientId;
  }

  return availableClientIds[0] ?? data.clients[0]?.id ?? requestedClientId;
}

function AuthStateScreen({
  title,
  description,
  error,
  loginUrl,
}: {
  title: string;
  description: string;
  error?: string | null;
  loginUrl?: string;
}) {
  return (
    <div className="launcher-app">
      <main
        style={{
          display: "grid",
          minHeight: "100vh",
          placeItems: "center",
          padding: "2rem",
        }}
      >
        <section
          style={{
            display: "grid",
            width: "min(34rem, 100%)",
            gap: "1rem",
            padding: "2rem",
            borderRadius: "1.75rem",
            background: "rgba(255, 255, 255, 0.08)",
            textAlign: "center",
          }}
        >
          <img src="/nodedc-logo.svg" alt="NODE.DC" style={{ justifySelf: "center", width: "11rem" }} />
          <h1 style={{ margin: 0 }}>{title}</h1>
          <p style={{ margin: 0, color: "var(--text-secondary)", lineHeight: 1.5 }}>{description}</p>
          {error ? <p style={{ margin: 0, color: "var(--warning)", lineHeight: 1.45 }}>{error}</p> : null}
          {loginUrl ? (
            <button className="button button--primary" type="button" onClick={() => window.location.assign(loginUrl)}>
              Войти
            </button>
          ) : null}
        </section>
      </main>
    </div>
  );
}

function buildLoginRedirectUrl(loginUrl?: string) {
  const url = new URL(loginUrl || "/auth/login", window.location.origin);

  if (!url.searchParams.has("returnTo")) {
    const returnTo = `${window.location.pathname}${window.location.search}${window.location.hash}`;

    if (returnTo && returnTo !== "/") {
      url.searchParams.set("returnTo", returnTo);
    }
  }

  return url.origin === window.location.origin ? `${url.pathname}${url.search}${url.hash}` : url.toString();
}
