import { memo, useEffect, useMemo, useState, type ReactNode } from 'react'
import { useMutation } from '@apollo/client/react'
import {
  Check,
  CircleHelp,
  Lock,
  Pencil,
  Power,
  PowerOff,
  Smartphone,
  X,
} from 'lucide-react'

import type { Device } from '../../types'
import {
  CHANGE_PHONE_POLICY_MUTATION,
} from '../../../../entities/device/api/device.graphql'
import type {
  ChangePhonePolicyData,
  ChangePhonePolicyVariables,
  ComponentUseOption,
  DevicePolicy,
  GroupUsePolicy,
  ServiceUseOption,
} from '../../../../entities/device/model/types'
import {
  normalizeComponentUseOption,
  normalizeServiceUseOption,
} from '../../../../entities/device/lib/phonePolicy'

import { BluetoothIcon } from '../../../../assets/icons/Bluetooth'
import { CameraIcon } from '../../../../assets/icons/Camera'
import { GpsIcon } from '../../../../assets/icons/Gps'
import { SimIcon } from '../../../../assets/icons/Sim'
import {
  Notification,
  type NotificationVariant,
} from '../../../../widgets/Notification/Notification'

import './DevicePermissionsCard.scss'

type DevicePermissionsCardProps = {
  device: Device
  onEditingChange?: (isEditing: boolean) => void
  onRefetchPolicy?: () => Promise<DevicePolicy | null | undefined>
}

type DevicePolicyKey = 'bluetooth' | 'gps' | 'camera' | 'sim'
type ServicePolicyKey = Exclude<DevicePolicyKey, 'camera'>
type PolicyKind = 'service' | 'component'
type PolicyValue = ServiceUseOption | ComponentUseOption
type OrganisationPolicyEditableKey =
  | 'bluetoothEditable'
  | 'gpsEditable'
  | 'cameraEditable'
  | 'simEditable'

type DevicePolicyState = {
  bluetooth: ServiceUseOption
  gps: ServiceUseOption
  camera: ComponentUseOption
  sim: ServiceUseOption
  locked: boolean
}

type PermissionItemConfig = {
  key: DevicePolicyKey
  editableKey: OrganisationPolicyEditableKey
  kind: PolicyKind
  icon: ReactNode
  label: string
}

type PolicyNotification = {
  id: number
  variant: NotificationVariant
  title: string
  description?: string
} | null

const SERVICE_POLICY_OPTION_ORDER: ServiceUseOption[] = [
  'Disabled',
  'Unspecified',
  'Enabled',
]
const COMPONENT_POLICY_OPTION_ORDER: ComponentUseOption[] = [
  'Disallowed',
  'Allowed',
]

const policyOptionFullLabels: Record<PolicyValue, string> = {
  Disabled: 'Выключено',
  Unspecified: 'Свободно',
  Enabled: 'Включено',
  Allowed: 'Включено',
  Disallowed: 'Выключено',
}

const policyOptionDescriptions: Record<PolicyValue, string> = {
  Disabled: 'Опция отключена политикой устройства.',
  Unspecified: 'Политика устройства не фиксирует значение. Можно менять на смартфоне.',
  Enabled: 'Опция включена политикой устройства.',
  Allowed: 'Компонент разрешен политикой устройства.',
  Disallowed: 'Компонент запрещен политикой устройства.',
}

const permissionItems: PermissionItemConfig[] = [
  {
    key: 'bluetooth',
    editableKey: 'bluetoothEditable',
    kind: 'service',
    icon: <BluetoothIcon />,
    label: 'Bluetooth',
  },
  {
    key: 'gps',
    editableKey: 'gpsEditable',
    kind: 'service',
    icon: <GpsIcon />,
    label: 'GPS',
  },
  {
    key: 'camera',
    editableKey: 'cameraEditable',
    kind: 'component',
    icon: <CameraIcon />,
    label: 'Камера',
  },
  {
    key: 'sim',
    editableKey: 'simEditable',
    kind: 'service',
    icon: <SimIcon />,
    label: 'SIM-карта',
  },
]

function getServicePolicyKey(key: DevicePolicyKey): ServicePolicyKey | null {
  return key === 'camera' ? null : key
}

function getPolicyOptions(kind: PolicyKind): PolicyValue[] {
  return kind === 'service'
    ? SERVICE_POLICY_OPTION_ORDER
    : COMPONENT_POLICY_OPTION_ORDER
}

function getPolicyOptionIcon(option: PolicyValue, size = 14) {
  if (option === 'Unspecified') {
    return <Smartphone size={size} aria-hidden="true" />
  }

  if (option === 'Enabled' || option === 'Allowed') {
    return <Power size={size} aria-hidden="true" />
  }

  return <PowerOff size={size} aria-hidden="true" />
}

function isOrganisationPolicyLocked(
  policy: GroupUsePolicy | null | undefined,
  key: OrganisationPolicyEditableKey,
) {
  return policy?.[key] === false
}

function mapDevicePermissionsToState(device: Device): DevicePolicyState {
  const policy = device.policy
  const permissions = device.permissions

  return {
    bluetooth: normalizeServiceUseOption(
      policy?.bluetooth ?? policy?.canUseBluetooth,
      permissions?.bluetooth,
    ),
    gps: normalizeServiceUseOption(
      policy?.GPS ?? policy?.canUseGPS,
      permissions?.gps,
    ),
    camera: normalizeComponentUseOption(
      policy?.camera ?? policy?.canUseCamera,
      permissions?.camera,
    ),
    sim: normalizeServiceUseOption(
      policy?.sim ?? policy?.canUseSim,
      permissions?.sim,
    ),
    locked: policy?.locked ?? permissions?.locked ?? false,
  }
}

function mapApiPolicyToState(
  policy: DevicePolicy | null | undefined,
  fallback: DevicePolicyState,
): DevicePolicyState {
  return {
    bluetooth: normalizeServiceUseOption(
      policy?.bluetooth ?? policy?.canUseBluetooth ?? fallback.bluetooth,
    ),
    gps: normalizeServiceUseOption(
      policy?.GPS ?? policy?.canUseGPS ?? fallback.gps,
    ),
    camera: normalizeComponentUseOption(
      policy?.camera ?? policy?.canUseCamera ?? fallback.camera,
    ),
    sim: normalizeServiceUseOption(
      policy?.sim ?? policy?.canUseSim ?? fallback.sim,
    ),
    locked: policy?.locked ?? fallback.locked,
  }
}

function mapStateToMutationVariables(
  policy: DevicePolicyState,
): Omit<ChangePhonePolicyVariables, 'id'> {
  return {
    bluetooth: policy.bluetooth,
    camera: policy.camera,
    GPS: policy.gps,
    sim: policy.sim,
    locked: policy.locked,
  }
}

function isPolicyChanged(
  current: DevicePolicyState,
  saved: DevicePolicyState,
) {
  return (
    current.bluetooth !== saved.bluetooth ||
    current.gps !== saved.gps ||
    current.camera !== saved.camera ||
    current.sim !== saved.sim
  )
}

function areDevicePermissionsCardPropsEqual(
  prev: DevicePermissionsCardProps,
  next: DevicePermissionsCardProps,
) {
  const prevPolicy = prev.device.policy
  const nextPolicy = next.device.policy
  const prevPermissions = prev.device.permissions
  const nextPermissions = next.device.permissions
  const prevOrganisationPolicy = prev.device.organisationPolicy
  const nextOrganisationPolicy = next.device.organisationPolicy

  return (
    prev.onEditingChange === next.onEditingChange &&
    prev.onRefetchPolicy === next.onRefetchPolicy &&
    prev.device.id === next.device.id &&
    prevPolicy?.bluetooth === nextPolicy?.bluetooth &&
    prevPolicy?.camera === nextPolicy?.camera &&
    prevPolicy?.GPS === nextPolicy?.GPS &&
    prevPolicy?.sim === nextPolicy?.sim &&
    prevPolicy?.locked === nextPolicy?.locked &&
    prevPermissions?.bluetooth === nextPermissions?.bluetooth &&
    prevPermissions?.camera === nextPermissions?.camera &&
    prevPermissions?.gps === nextPermissions?.gps &&
    prevPermissions?.sim === nextPermissions?.sim &&
    prevPermissions?.locked === nextPermissions?.locked &&
    prevOrganisationPolicy?.bluetoothEditable ===
      nextOrganisationPolicy?.bluetoothEditable &&
    prevOrganisationPolicy?.cameraEditable ===
      nextOrganisationPolicy?.cameraEditable &&
    prevOrganisationPolicy?.gpsEditable ===
      nextOrganisationPolicy?.gpsEditable &&
    prevOrganisationPolicy?.simEditable === nextOrganisationPolicy?.simEditable
  )
}

export const DevicePermissionsCard = memo(function DevicePermissionsCard({
  device,
  onEditingChange,
  onRefetchPolicy,
}: DevicePermissionsCardProps) {
  const initialPolicy = useMemo(() => {
    return mapDevicePermissionsToState(device)
  }, [device])

  const [policyState, setPolicyState] = useState<DevicePolicyState>(initialPolicy)
  const [savedPolicyState, setSavedPolicyState] =
    useState<DevicePolicyState>(initialPolicy)
  const [isSaving, setIsSaving] = useState(false)
  const [isEditing, setIsEditing] = useState(false)
  const [saveError, setSaveError] = useState(false)
  const [notification, setNotification] =
    useState<PolicyNotification>(null)

  const [changePhonePolicy] = useMutation<
    ChangePhonePolicyData,
    ChangePhonePolicyVariables
  >(CHANGE_PHONE_POLICY_MUTATION)

  const hasChanges = isPolicyChanged(policyState, savedPolicyState)
  const hasHeaderActionsOpen = isEditing || hasChanges

  useEffect(() => {
    if (isEditing || isSaving) return

    setPolicyState(initialPolicy)
    setSavedPolicyState(initialPolicy)
    setSaveError(false)
    setIsEditing(false)
  }, [initialPolicy, isEditing, isSaving])

  useEffect(() => {
    onEditingChange?.(isEditing)

    return () => {
      if (isEditing) {
        onEditingChange?.(false)
      }
    }
  }, [isEditing, onEditingChange])

  function showNotification(
    variant: NotificationVariant,
    title: string,
    description?: string,
  ) {
    setNotification({
      id: Date.now(),
      variant,
      title,
      description,
    })
  }

  function handlePolicyOptionChange(
    item: PermissionItemConfig,
    option: PolicyValue,
    disabled?: boolean,
  ) {
    if (disabled || !isEditing || isSaving) return

    setPolicyState((currentPolicy) => {
      if (item.kind === 'component') {
        if (!COMPONENT_POLICY_OPTION_ORDER.includes(option as ComponentUseOption)) {
          return currentPolicy
        }

        return {
          ...currentPolicy,
          camera: option as ComponentUseOption,
        }
      }

      const key = getServicePolicyKey(item.key)

      if (
        !key ||
        !SERVICE_POLICY_OPTION_ORDER.includes(option as ServiceUseOption)
      ) {
        return currentPolicy
      }

      return {
        ...currentPolicy,
        [key]: option as ServiceUseOption,
      }
    })
    setSaveError(false)
  }

  function handleReset() {
    if ((!isEditing && !hasChanges) || isSaving) return

    setPolicyState(savedPolicyState)
    setSaveError(false)
    setIsEditing(false)
  }

  function handleEditClick() {
    setIsEditing(true)
  }

  async function handleConfirmClick() {
    if (isSaving) return

    if (!hasChanges) {
      setIsEditing(false)
      setSaveError(false)
      return
    }

    await handleSave()
  }

  async function handleSave() {
    if (!hasChanges || isSaving) return

    const requestedPolicy = policyState

    setIsSaving(true)
    setSaveError(false)

    try {
      const result = await changePhonePolicy({
        variables: {
          id: String(device.id),
          ...mapStateToMutationVariables(requestedPolicy),
        },
      })

      const changedApiPolicy = result.data?.changePhonePolicy

      if (!changedApiPolicy) {
        throw new Error('Phone policy was not saved')
      }

      const refreshedApiPolicy = await onRefetchPolicy?.()
      const refreshedPolicy = mapApiPolicyToState(
        refreshedApiPolicy ?? changedApiPolicy,
        requestedPolicy,
      )

      setPolicyState(refreshedPolicy)
      setSavedPolicyState(refreshedPolicy)

      if (isPolicyChanged(requestedPolicy, refreshedPolicy)) {
        setSaveError(true)
        showNotification(
          'error',
          'Политика не изменилась',
          'Сервер ответил без ошибки, но после повторной загрузки вернулась прежняя политика.',
        )
        return
      }

      setIsEditing(false)
      showNotification('success', 'Политика устройства сохранена')
    } catch {
      setSaveError(true)
      showNotification(
        'error',
        'Не удалось сохранить политику устройства',
        'Изменения остались в черновике. Попробуйте сохранить еще раз.',
      )
    } finally {
      setIsSaving(false)
    }
  }

  return (
    <div
      className={`device-card device-permissions ${
        hasHeaderActionsOpen ? 'is-editing' : ''
      } ${isEditing ? 'is-policy-editing' : ''}`}
    >
      <div className="device-permissions__header">
        <div className="device-permissions__title">
          <h3>Опции</h3>

          <span
            className="device-permissions__help"
            tabIndex={0}
            aria-label="Пояснение значений опций"
          >
            <CircleHelp size={16} />

            <span className="device-permissions__help-tooltip" role="tooltip">
              <span className="device-permissions__help-row">
                {getPolicyOptionIcon('Disabled', 15)}
                <span>
                  <b>Выключено</b>
                  <small>Опция отключена политикой устройства.</small>
                </span>
              </span>
              <span className="device-permissions__help-row">
                {getPolicyOptionIcon('Unspecified', 15)}
                <span>
                  <b>Свободно</b>
                  <small>Можно включить или выключить на смартфоне.</small>
                </span>
              </span>
              <span className="device-permissions__help-row">
                {getPolicyOptionIcon('Enabled', 15)}
                <span>
                  <b>Включено</b>
                  <small>Опция включена политикой устройства.</small>
                </span>
              </span>
            </span>
          </span>
        </div>

        <div className="device-permissions__header-actions">
          <button
            className="device-permissions__icon-btn device-permissions__icon-btn--reset"
            type="button"
            disabled={(!isEditing && !hasChanges) || isSaving}
            onClick={handleReset}
            aria-label="Отменить редактирование"
          >
            <X size={18} />
          </button>

          <button
            className="device-permissions__icon-btn device-permissions__icon-btn--edit"
            type="button"
            disabled={isSaving}
            onClick={
              hasHeaderActionsOpen ? handleConfirmClick : handleEditClick
            }
            aria-label={
              hasHeaderActionsOpen
                ? 'Сохранить изменения'
                : 'Редактировать опции'
            }
          >
            {hasHeaderActionsOpen ? <Check size={18} /> : <Pencil size={18} />}
          </button>
        </div>
      </div>

      <div className="device-permissions__list">
        {permissionItems.map((item) => {
          const value = policyState[item.key]
          const isPolicyLocked = isOrganisationPolicyLocked(
            device.organisationPolicy,
            item.editableKey,
          )
          const isDisabled = isPolicyLocked || !isEditing || isSaving

          return (
            <PermissionItem
              key={item.key}
              icon={item.icon}
              label={item.label}
              kind={item.kind}
              value={value}
              options={getPolicyOptions(item.kind)}
              disabled={isDisabled}
              isPolicyLocked={isPolicyLocked}
              onChange={(option) => {
                handlePolicyOptionChange(item, option, isPolicyLocked)
              }}
            />
          )
        })}
      </div>

      {saveError && (
        <div className="device-permissions__error">
          Не удалось сохранить политику устройства
        </div>
      )}

      {notification && (
        <Notification
          key={notification.id}
          variant={notification.variant}
          title={notification.title}
          description={notification.description}
          onClose={() => setNotification(null)}
        />
      )}
    </div>
  )
}, areDevicePermissionsCardPropsEqual)

type PermissionItemProps = {
  icon: ReactNode
  label: string
  kind: PolicyKind
  value: PolicyValue
  options: PolicyValue[]
  disabled?: boolean
  isPolicyLocked?: boolean
  onChange: (value: PolicyValue) => void
}

function PermissionItem({
  icon,
  label,
  kind,
  value,
  options,
  disabled = false,
  isPolicyLocked = false,
  onChange,
}: PermissionItemProps) {
  return (
    <div
      className={`device-permission ${
        isPolicyLocked ? 'is-policy-locked' : ''
      }`}
      aria-disabled={disabled}
    >
      {icon}

      <div className="device-permission__label">
        <span>{label}</span>

        <span className="device-permission__controls">
          {isPolicyLocked && (
            <span
              className="device-permission__policy-lock"
              tabIndex={0}
              aria-label="Опция зафиксирована политикой организации"
            >
              <Lock size={14} aria-hidden="true" />

              <span
                className="device-permission__policy-lock-tooltip"
                role="tooltip"
              >
                Опция зафиксирована политикой организации. Изменить ее на странице устройства нельзя.
              </span>
            </span>
          )}

          <div
            className={`device-permission__switch device-permission__switch--${kind} is-${value.toLowerCase()} ${
              disabled ? 'is-disabled' : ''
            }`}
            role="radiogroup"
            aria-label={`${label}: ${policyOptionFullLabels[value]}`}
            aria-disabled={disabled}
          >
            <span
              className="device-permission__switch-thumb"
              aria-hidden="true"
            />

            {options.map((option) => {
              const isActive = value === option

              return (
                <button
                  className={`device-permission__switch-option ${
                    isActive ? 'is-active' : ''
                  }`}
                  key={option}
                  type="button"
                  role="radio"
                  aria-checked={isActive}
                  aria-disabled={disabled}
                  aria-label={`${policyOptionFullLabels[option]}: ${policyOptionDescriptions[option]}`}
                  tabIndex={disabled ? -1 : 0}
                  onClick={() => {
                    if (!disabled && !isActive) {
                      onChange(option)
                    }
                  }}
                >
                  <span className="device-permission__switch-label">
                    {getPolicyOptionIcon(option)}
                  </span>
                </button>
              )
            })}
          </div>
        </span>
      </div>
    </div>
  )
}
