import React, { useState } from 'react';
import store from 'app/core/store';
import { config } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv';
import { dateTime } from '@grafana/data';
import { IsInvalid, HasExpired, ExpiresSoon, TokenExpiresSoon, MaxUsersReached } from './LicenseWarning';
import { OrgRole } from 'app/types';
import { AccessControlAction } from '../types';
import { refreshLicenseStats } from './state/api';
import { addBodyRenderHook } from 'app/AppWrapper';
import { ActiveUserStats } from './types';
import {
  DISMISS_WARNING_FOR_DAYS,
  LICENSE_WARNING_DISMISS_UNTIL_KEY,
  WARNING_CLOSE_TIMEOUT_SEC,
  LIMIT_BY_ROLE_USERS,
  LIMIT_BY_USERS,
} from './constants';

interface LicensingSettings {
  activeAdminsAndEditors?: number;
  activeViewers?: number;
  activeUsers?: number;
  limitBy?: string;
  includedAdmins?: number;
  includedViewers?: number;
  includedUsers?: number;
  slug?: string;
  licenseExpiry?: number;
  licenseExpiryWarnDays?: number;
  tokenExpiry?: number;
  tokenExpiryWarnDays?: number;
  usageBilling?: boolean;
}

export function initLicenseWarnings() {
  addBodyRenderHook(LicenseWarning);
}

export function LicenseWarning() {
  const [isClosed, setIsClosed] = useState(false);
  const [settings, updateSettings] = useState((config as any).licensing as LicensingSettings);

  const dismissUntil = store.get(LICENSE_WARNING_DISMISS_UNTIL_KEY);
  const hasDismissed = dismissUntil && dismissUntil > dateTime().valueOf();
  // true if the user has licensing:read permission if FGAC is enabled or is an organisation admin if FGAC is disabled
  const isLicensingReaderOrAdmin = contextSrv.hasAccess(
    AccessControlAction.LicensingRead,
    contextSrv.hasRole(OrgRole.Admin)
  );
  // true if the user has licensing:read permission if FGAC is enabled or is a Grafana server admin if FGAC is disabled
  const isLicensingReaderOrGrafanaAdmin = contextSrv.hasAccess(
    AccessControlAction.LicensingRead,
    contextSrv.isGrafanaAdmin
  );
  const showExpireSoon = isLicensingReaderOrAdmin && willExpireSoon() && !hasDismissed;
  const showTokenExpireSoon = isLicensingReaderOrAdmin && tokenWillExpireSoon() && !hasDismissed;
  const usageBillingDisabled = isUsageBillingDisabled();
  let showMaxUsersReached = false;
  let activeUsers = 0;
  let maxUsers = 0;
  let limitType = '';
  if (settings.limitBy === LIMIT_BY_ROLE_USERS) {
    const maxAdminsReached = numberOfActiveUsersReached(settings.includedAdmins, settings.activeAdminsAndEditors);
    const maxViewersReached = numberOfActiveUsersReached(settings.includedViewers, settings.activeViewers);

    showMaxUsersReached =
      usageBillingDisabled &&
      (maxAdminsReached || maxViewersReached) &&
      (isLicensingReaderOrAdmin || isLicensingReaderOrGrafanaAdmin);
    activeUsers = maxAdminsReached ? settings.activeAdminsAndEditors! : settings.activeViewers!;
    maxUsers = maxAdminsReached ? settings.includedAdmins! : settings.includedViewers!;
    limitType = maxAdminsReached ? 'admins / editors' : 'viewers';
  } else if (settings.limitBy === LIMIT_BY_USERS) {
    const maxUsersReached = numberOfActiveUsersReached(settings.includedUsers, settings.activeUsers);

    showMaxUsersReached =
      usageBillingDisabled && maxUsersReached && (isLicensingReaderOrAdmin || isLicensingReaderOrGrafanaAdmin);
    activeUsers = settings.activeUsers!;
    maxUsers = settings.includedUsers!;
    limitType = 'users';
  }

  if (isRenderingPanel() || isLicenseAdminPage()) {
    return null;
  }

  const onCloseWarning = () => {
    const dismissTill = dateTime().add(DISMISS_WARNING_FOR_DAYS, 'd').valueOf();
    store.set(LICENSE_WARNING_DISMISS_UNTIL_KEY, dismissTill);
    setIsClosed(true);
  };

  const onRefreshWarning = async () => {
    const activeUserStats: ActiveUserStats | null = await refreshLicenseStats().catch((err) => null);

    if (activeUserStats) {
      const update = {
        ...settings,
        activeAdminsAndEditors: activeUserStats.active_admins_and_editors,
        activeViewers: activeUserStats.active_viewers,
      };

      // update on config object as well
      (config as any).licensing = update;

      updateSettings(update);
    }
  };

  if (isClosed) {
    return null;
  }

  if (isInvalid()) {
    return <IsInvalid isLicensingReader={isLicensingReaderOrGrafanaAdmin} />;
  } else if (hasExpired()) {
    return <HasExpired isLicensingReader={isLicensingReaderOrGrafanaAdmin} />;
  } else if (showMaxUsersReached) {
    return (
      <MaxUsersReached
        activeUsers={activeUsers}
        maxUsers={maxUsers}
        type={limitType}
        slug={settings.slug}
        onRefreshWarning={isLicensingReaderOrAdmin ? onRefreshWarning : undefined}
      />
    );
  } else if (showExpireSoon) {
    const expiresIn = willExpireInDays();

    // auto hide expire warning in case it's a TV monitor with admin permissions
    setTimeout(onCloseWarning, 1000 * WARNING_CLOSE_TIMEOUT_SEC);

    return (
      <ExpiresSoon
        days={expiresIn}
        onCloseWarning={onCloseWarning}
        isLicensingReader={isLicensingReaderOrGrafanaAdmin}
      />
    );
  } else if (showTokenExpireSoon) {
    const expiresIn = tokenWillExpireInDays();

    // auto hide expire warning in case it's a TV monitor with admin permissions
    setTimeout(onCloseWarning, 1000 * WARNING_CLOSE_TIMEOUT_SEC);

    return (
      <TokenExpiresSoon
        days={expiresIn}
        onCloseWarning={onCloseWarning}
        isLicensingReader={isLicensingReaderOrGrafanaAdmin}
      />
    );
  }

  return null;
}

export function isInvalid(): boolean {
  const { expiry, hasLicense } = (config as any).licenseInfo;
  return hasLicense && !expiry;
}

export function willExpireSoon(): boolean {
  const { licenseExpiry, licenseExpiryWarnDays = 30 } = (config as any).licensing;
  return licenseExpiry > 0 && dateTime(licenseExpiry * 1000) < dateTime().add(licenseExpiryWarnDays, 'd');
}

export function willExpireInDays(): number {
  const { licenseExpiry } = (config as any).licensing;
  return Math.ceil((licenseExpiry - dateTime().unix()) / 3600 / 24);
}

export function hasExpired(): boolean {
  const { licenseExpiry } = (config as any).licensing;
  return licenseExpiry > 0 && dateTime(licenseExpiry * 1000) < dateTime();
}

function tokenWillExpireSoon(): boolean {
  const { tokenExpiry, tokenExpiryWarnDays = 3 } = (config as any).licensing;
  return tokenExpiry > 0 && dateTime(tokenExpiry * 1000) < dateTime().add(tokenExpiryWarnDays, 'd');
}

function tokenWillExpireInDays(): number {
  const { tokenExpiry } = (config as any).licensing;
  return Math.ceil((tokenExpiry - dateTime().unix()) / 3600 / 24);
}

export function numberOfActiveUsersReached(includedUsers?: number, activeUsers?: number): boolean {
  if (includedUsers === undefined || activeUsers === undefined) {
    return false;
  }
  return includedUsers !== -1 && activeUsers > includedUsers;
}

function isUsageBillingDisabled(): boolean {
  const settings = (config as any).licensing as LicensingSettings;
  return !settings.usageBilling;
}

function isSoloPanel(): boolean {
  const soloPanelPattern = /\/d-solo\//;
  const path = window.location.pathname;
  return soloPanelPattern.test(path);
}

function isRenderingPanel(): boolean {
  return isSoloPanel();
}

function isLicenseAdminPage(): boolean {
  const pattern = /\/admin\/licensing$/;
  const path = window.location.pathname;
  return pattern.test(path);
}
