/**
 * ExposeSettings — Home Assistant Expose entity management (M5-T12).
 *
 * Behaviours:
 * 1. Load: GET /api/expose → display catalog of exposable entities grouped by HA device,
 *    with per-entity toggle switches and MQTT/Discovery connection status badges.
 * 2. Toggle: PUT /api/expose with the changed key → bool; triggers discovery re-publish.
 * 3. Republish: POST /api/expose/republish → manually trigger HA Discovery re-publish.
 *
 * All requests go through the typed apiClient (CSRF injected automatically by
 * csrfMiddleware for write operations).
 */

import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import {
  Alert,
  Badge,
  Button,
  Center,
  Group,
  Loader,
  Stack,
  Switch,
  Text,
} from '@mantine/core'
import apiClient from '../api/client'
import type { components } from '../api/schema.d.ts'

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

type CatalogEntry = components['schemas']['CatalogEntrySchema']
type MqttStatus = components['schemas']['MqttStatusSchema']

// ---------------------------------------------------------------------------
// Hook: load expose catalog
// ---------------------------------------------------------------------------

function useExposeCatalog() {
  return useQuery({
    queryKey: ['expose-catalog'],
    queryFn: async () => {
      const res = await apiClient.GET('/api/expose')
      return res.data
    },
  })
}

// ---------------------------------------------------------------------------
// Hook: toggle a single entity
// ---------------------------------------------------------------------------

function useToggleEntity() {
  const qc = useQueryClient()
  return useMutation({
    mutationFn: async ({ key, enabled }: { key: string; enabled: boolean }) => {
      await apiClient.PUT('/api/expose', {
        body: { toggles: { [key]: enabled } },
      })
    },
    onSuccess: () => qc.invalidateQueries({ queryKey: ['expose-catalog'] }),
  })
}

// ---------------------------------------------------------------------------
// Hook: republish discovery
// ---------------------------------------------------------------------------

function useRepublish() {
  const qc = useQueryClient()
  return useMutation({
    mutationFn: async () => {
      const res = await apiClient.POST('/api/expose/republish')
      return res.data
    },
    onSuccess: () => qc.invalidateQueries({ queryKey: ['expose-catalog'] }),
  })
}

// ---------------------------------------------------------------------------
// MqttStatusBadges — connection status indicators
// ---------------------------------------------------------------------------

function MqttStatusBadges({ status }: { status: MqttStatus }) {
  return (
    <Group gap="xs" wrap="wrap" data-testid="mqtt-status-badges">
      <Badge
        color={status.mqtt_configured ? 'blue' : 'gray'}
        variant="outline"
        size="sm"
        data-testid="badge-mqtt-configured"
      >
        MQTT {status.mqtt_configured ? 'Configured' : 'Not Configured'}
      </Badge>
      <Badge
        color={status.mqtt_connected ? 'green' : 'red'}
        variant="outline"
        size="sm"
        data-testid="badge-mqtt-connected"
      >
        {status.mqtt_connected ? 'Connected' : 'Disconnected'}
      </Badge>
      <Badge
        color={status.discovery_enabled ? 'teal' : 'gray'}
        variant="outline"
        size="sm"
        data-testid="badge-discovery-enabled"
      >
        Discovery {status.discovery_enabled ? 'On' : 'Off'}
      </Badge>
    </Group>
  )
}

// ---------------------------------------------------------------------------
// EntityRow — one entity toggle row
// ---------------------------------------------------------------------------

interface EntityRowProps {
  entry: CatalogEntry
  onToggle: (key: string, enabled: boolean) => void
  isToggling: boolean
}

function EntityRow({ entry, onToggle, isToggling }: EntityRowProps) {
  const { entity, enabled } = entry
  return (
    <Group justify="space-between" gap="sm" wrap="nowrap" data-testid={`entity-row-${entity.key}`}>
      <Stack gap={2}>
        <Text size="sm" fw={500}>
          {entity.name}
        </Text>
        <Text size="xs" c="dimmed">
          {entity.component}
          {entity.device_class ? ` · ${entity.device_class}` : ''}
          {entity.unit ? ` · ${entity.unit}` : ''}
        </Text>
      </Stack>
      <Switch
        checked={enabled}
        onChange={(e) => onToggle(entity.key, e.currentTarget.checked)}
        disabled={isToggling}
        size="sm"
        data-testid={`entity-toggle-${entity.key}`}
      />
    </Group>
  )
}

// ---------------------------------------------------------------------------
// DeviceGroup — entities grouped by device name
// ---------------------------------------------------------------------------

interface DeviceGroupProps {
  deviceName: string
  entries: CatalogEntry[]
  onToggle: (key: string, enabled: boolean) => void
  isToggling: boolean
}

function DeviceGroup({ deviceName, entries, onToggle, isToggling }: DeviceGroupProps) {
  return (
    <Stack gap="xs" data-testid={`device-group-${deviceName}`}>
      <Text fw={600} size="sm" c="dimmed">
        {deviceName}
      </Text>
      {entries.map((entry) => (
        <EntityRow
          key={entry.entity.key}
          entry={entry}
          onToggle={onToggle}
          isToggling={isToggling}
        />
      ))}
    </Stack>
  )
}

// ---------------------------------------------------------------------------
// RepublishButton — POST /api/expose/republish
// ---------------------------------------------------------------------------

function RepublishButton() {
  const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null)
  const republishMutation = useRepublish()

  async function handleRepublish() {
    setResult(null)
    try {
      const data = await republishMutation.mutateAsync()
      if (data) {
        setResult({ ok: data.ok, message: data.message })
      }
    } catch {
      setResult({ ok: false, message: 'Republish request failed.' })
    }
  }

  return (
    <Stack gap="xs">
      <Button
        type="button"
        variant="outline"
        size="sm"
        onClick={handleRepublish}
        loading={republishMutation.isPending}
        data-testid="republish-button"
      >
        Re-publish Discovery
      </Button>

      {result?.ok && (
        <Alert color="green" data-testid="republish-success">
          {result.message}
        </Alert>
      )}
      {result !== null && !result.ok && (
        <Alert color="orange" data-testid="republish-warning">
          {result.message}
        </Alert>
      )}
    </Stack>
  )
}

// ---------------------------------------------------------------------------
// ExposeSettings — main exported component
// ---------------------------------------------------------------------------

export function ExposeSettings() {
  const { data, isLoading, isError } = useExposeCatalog()
  const toggleMutation = useToggleEntity()

  function handleToggle(key: string, enabled: boolean) {
    toggleMutation.mutate({ key, enabled })
  }

  if (isLoading) {
    return (
      <Center pt="md">
        <Loader size="sm" data-testid="expose-loading" />
      </Center>
    )
  }

  if (isError || !data) {
    return (
      <Alert color="red" data-testid="expose-load-error">
        Failed to load expose settings. Please refresh the page.
      </Alert>
    )
  }

  // Defensive null-coerce in case the data shape is unexpected at runtime.
  const catalog = data.catalog ?? []
  const mqtt_status = data.mqtt_status ?? {
    mqtt_configured: false,
    mqtt_connected: false,
    discovery_enabled: false,
  }

  // Group entities by device name for display.
  const deviceGroups = new Map<string, CatalogEntry[]>()
  for (const entry of catalog) {
    const deviceName = entry.entity.device.name
    const existing = deviceGroups.get(deviceName)
    if (existing) {
      existing.push(entry)
    } else {
      deviceGroups.set(deviceName, [entry])
    }
  }

  return (
    <Stack gap="md" data-testid="expose-settings">
      {/* Status row */}
      <Group justify="space-between" align="center" wrap="wrap" gap="sm">
        <MqttStatusBadges status={mqtt_status} />
        <RepublishButton />
      </Group>

      {/* Entity catalog — empty state */}
      {catalog.length === 0 && (
        <Text c="dimmed" size="sm" data-testid="expose-empty">
          No exposable entities found. Add a Modbus device in the Energy page to get started.
        </Text>
      )}

      {/* Entity catalog — grouped by device */}
      {Array.from(deviceGroups.entries()).map(([deviceName, entries]) => (
        <DeviceGroup
          key={deviceName}
          deviceName={deviceName}
          entries={entries}
          onToggle={handleToggle}
          isToggling={toggleMutation.isPending}
        />
      ))}
    </Stack>
  )
}
