import { useCallback, useEffect, useMemo, useState } from 'react';

import pluralize from 'pluralize';

import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutlineOutlined';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import SearchIcon from '@mui/icons-material/Search';
import { Alert, Box, Button, ButtonBase, Collapse, InputAdornment, Link, Tab, Tabs, TextField, Tooltip, Typography } from '@mui/material';
import { useDebounce } from '@northern.tech/utils/debouncehook';

import { blue, darkBackground, gray, red } from '../../../../themes/common';
import CommonDialog from '../../Dialog';
import { Pagination } from '../../Pagination';
import { searchHostsByInventory } from '../api';
import { DEFAULT_SEARCH_RESULT_COUNT, SEARCH_DEBOUNCE_DELAY, VALIDATION_PATTERNS } from '../constants';
import { isIpAddressInSubnet } from '../utils/ipUtils';
import { emptyResolvedEntries, entriesCount, resolveEntries, resolvedHostkeys, type ResolvedEntries } from '../utils/resolveEntries';
import type { EntriesMap, EntryType, HostEntry } from '../types';

interface HostSelectionDialogProps {
  entries: EntriesMap;
  include: boolean;
  onClose: () => void;
  onSave: (entries: EntriesMap) => void;
}

// The four selectable tabs; IP holds both `ip` and `ip_mask` entries.
type TabType = 'hostkey' | 'ip' | 'hostname' | 'mac';

const classifyQuery = (query: string) => {
  const isIpMask = VALIDATION_PATTERNS.IP_MASK.test(query);
  const isIp = VALIDATION_PATTERNS.IP.test(query);
  const isHostname = VALIDATION_PATTERNS.HOSTNAME.test(query);
  return { isIp, isIpMask, isHostname: !isIp && isHostname };
};

const addUnique = (values: string[] | undefined, value: string) => Array.from(new Set([value, ...(values || [])]));

const splitValues = (raw: unknown): string[] =>
  String(raw ?? '')
    .split(', ')
    .map(value => value.trim())
    .filter(Boolean);

// Which host attribute matched the search, for the "(attr: value)" hint.
const matchTip = (host: HostEntry, query: string, isIpMask: boolean): { attr: string; value: string } | null => {
  if (!query) return null;
  const attrs: [string, string][] = [
    ['ip', host.ip ?? ''],
    ['mac', host.mac ?? ''],
    ['hostname', host.hostname ?? ''],
    ['hostkey', host.hostkey ?? '']
  ];
  for (const [attr, raw] of attrs) {
    for (const value of splitValues(raw)) {
      if (isIpMask ? attr === 'ip' && isIpAddressInSubnet(value, query) : value.includes(query)) {
        return { attr, value };
      }
    }
  }
  return null;
};

// Tooltip listing the hosts a subnet resolves to (first 5).
const ipMaskDescription = (subnet: string, resolved: ResolvedEntries): string => {
  const data = resolved.ip_mask[subnet];
  if (!data || data.length === 0) {
    return 'There are currently no hosts in the database matching this entry, or they are not visible to you because of RBAC filtering.';
  }
  const lines = data.slice(0, 5).map(item => {
    const ip = splitValues(item.ip).find(single => isIpAddressInSubnet(single, subnet));
    return `${item.hostname} (IP: ${ip})`;
  });
  return data.length > 5 ? `${lines.join('\n')}\n ...` : lines.join('\n');
};

export const HostSelectionDialog = ({ include, entries, onClose, onSave }: HostSelectionDialogProps) => {
  const [search, setSearch] = useState('');
  const [results, setResults] = useState<HostEntry[]>([]);
  const [rowCount, setRowCount] = useState(0);
  const [page, setPage] = useState(1);
  const [error, setError] = useState<string | null>(null);
  const [draft, setDraft] = useState<EntriesMap>(entries);
  const [selectedType, setSelectedType] = useState<TabType>('hostkey');
  const [expanded, setExpanded] = useState<string | null>(null);
  const [resolved, setResolved] = useState<ResolvedEntries>(emptyResolvedEntries());

  const flags = useMemo(() => classifyQuery(search.trim()), [search]);
  const debouncedSearch = useDebounce(search, SEARCH_DEBOUNCE_DELAY);

  // Resolve the current entries to their hosts (for counts, names, tooltips).
  useEffect(() => {
    let active = true;
    resolveEntries(draft)
      .then(next => active && setResolved(next))
      .catch(() => active && setResolved(emptyResolvedEntries()));
    return () => {
      active = false;
    };
  }, [draft]);

  const runSearch = useCallback((query: string, nextPage: number) => {
    const offset = DEFAULT_SEARCH_RESULT_COUNT * (nextPage - 1);
    searchHostsByInventory(query, DEFAULT_SEARCH_RESULT_COUNT, offset, classifyQuery(query.trim()).isIpMask)
      .then(({ data, meta }) => {
        setResults(data);
        setRowCount(meta?.rowCount ?? data.length);
        setError(null);
      })
      .catch(() => {
        setResults([]);
        setRowCount(0);
        setError('Failed to search hosts. Please view server logs for details.');
      });
  }, []);

  // Debounced search whenever the query changes, and once on mount.
  useEffect(() => {
    setPage(1);
    setExpanded(null);
    runSearch(debouncedSearch.trim(), 1);
  }, [debouncedSearch, runSearch]);

  const isAdded = (type: EntryType, value: string) => Boolean(draft[type]?.includes(value));

  const addEntry = (type: EntryType, value: string) => {
    setDraft(current => ({ ...current, [type]: addUnique(current[type], value) }));
    setSelectedType(type === 'ip_mask' ? 'ip' : type);
  };

  const removeEntry = (type: EntryType, value: string) =>
    setDraft(current => ({ ...current, [type]: (current[type] || []).filter(item => item !== value) }));

  const handlePageChange = (nextPage: number) => {
    setPage(nextPage);
    setExpanded(null);
    runSearch(search.trim(), nextPage);
  };

  const totalEntries = entriesCount(draft);
  const hostsCount = resolvedHostkeys(resolved).length;
  const ipCount = (draft.ip?.length ?? 0) + (draft.ip_mask?.length ?? 0);
  const query = search.trim();

  return (
    <CommonDialog
      open
      onClose={onClose}
      title={include ? 'Include hosts' : 'Exclude hosts'}
      size="md"
      fullWidth
      actionButtons={
        <Button variant="contained" color="primary" onClick={() => onSave(draft)}>
          Confirm
        </Button>
      }
    >
      <Typography variant="body2" color="text.secondary" sx={{ borderBottom: 1, borderColor: 'divider', mb: 2, pb: 2 }}>
        {include
          ? 'Including hosts will cause them to be added to the filter results, even if they don\'t match the rules of the filter.'
          : 'Excluding hosts will remove them from the filter results.'}
      </Typography>

      <Box sx={{ display: 'flex', minHeight: 380, alignItems: 'stretch' }}>
        {/* Left: search + add-by-attribute */}
        <Box sx={{ flex: 1, minWidth: 0, borderRight: 1, borderColor: 'divider', pr: 3 }}>
          <TextField
            autoFocus
            size="small"
            placeholder="Search for hostname, ID, IP, MAC"
            sx={{ width: 290, maxWidth: '100%' }}
            value={search}
            onChange={event => setSearch(event.target.value)}
            slotProps={{
              input: {
                startAdornment: (
                  <InputAdornment position="start">
                    <SearchIcon fontSize="small" />
                  </InputAdornment>
                )
              }
            }}
          />

          {query && (flags.isIp || flags.isIpMask) && (
            <AddQuery
              label={`Add IP ${query}`}
              tooltip="The list will change dynamically if hosts appear with a matching IP address"
              onAdd={() => addEntry(flags.isIpMask ? 'ip_mask' : 'ip', query)}
            />
          )}
          {query && flags.isHostname && (
            <AddQuery
              label={`Add hostname "${query}"`}
              tooltip="The list will change dynamically if hosts appear with a matching hostname"
              onAdd={() => addEntry('hostname', query)}
            />
          )}

          {error && (
            <Alert severity="error" sx={{ mt: 2 }}>
              {error}
            </Alert>
          )}

          <Box sx={{ mt: 1, maxHeight: 300, overflowY: 'auto' }}>
            {results.length === 0 ? (
              <Typography variant="body2" color="text.secondary" sx={{ py: 2 }}>
                No hosts found.
              </Typography>
            ) : (
              results.map(host => (
                <HostResultRow
                  key={host.hostkey}
                  host={host}
                  tip={matchTip(host, query, flags.isIpMask)}
                  expanded={expanded === host.hostkey}
                  isAdded={isAdded}
                  onToggle={() => setExpanded(expanded === host.hostkey ? null : host.hostkey)}
                  onAdd={addEntry}
                />
              ))
            )}
          </Box>

          {rowCount > DEFAULT_SEARCH_RESULT_COUNT && (
            <Pagination
              align="left"
              count={rowCount}
              rowsPerPage={DEFAULT_SEARCH_RESULT_COUNT}
              page={page}
              rowsPerPageOptions={[DEFAULT_SEARCH_RESULT_COUNT]}
              onChangePage={handlePageChange}
              onChangeRowsPerPage={() => undefined}
            />
          )}
        </Box>

        {/* Right: current selection */}
        <Box sx={{ flex: 1, minWidth: 0, pl: 3 }}>
          <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
            <Typography variant="subtitle2" sx={{ display: 'flex', alignItems: 'center', gap: 0.5, color: 'text.primary' }}>
              {pluralize('entry', totalEntries, true)}, {pluralize('host', hostsCount, true)} in total
              <Tooltip title="Usually, the number of entries matches the number of hosts. If not, it could for example be due to multiple hosts having the given IP address, or if no hosts are currently reporting a specified hostname.">
                <InfoOutlinedIcon fontSize="small" color="action" />
              </Tooltip>
            </Typography>
            <Button
              size="small"
              color="inherit"
              startIcon={<DeleteOutlineIcon />}
              sx={{ color: 'text.primary', fontWeight: 500 }}
              disabled={totalEntries === 0}
              onClick={() => setDraft({})}
            >
              Clear all
            </Button>
          </Box>

          <Tabs value={selectedType} variant="fullWidth" sx={{ mt: 1, minHeight: 40 }} onChange={(_, value: TabType) => setSelectedType(value)}>
            <Tab value="hostkey" label={`ID(${draft.hostkey?.length ?? 0})`} sx={{ minHeight: 40 }} />
            <Tab value="ip" label={`IP(${ipCount})`} sx={{ minHeight: 40 }} />
            <Tab value="hostname" label={`Hostname(${draft.hostname?.length ?? 0})`} sx={{ minHeight: 40 }} />
            <Tab value="mac" label={`MAC(${draft.mac?.length ?? 0})`} sx={{ minHeight: 40 }} />
          </Tabs>

          <SelectedEntries selectedType={selectedType} draft={draft} resolved={resolved} onRemove={removeEntry} />
        </Box>
      </Box>
    </CommonDialog>
  );
};

const AddQuery = ({ label, tooltip, onAdd }: { label: string; onAdd: () => void; tooltip: string }) => (
  <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 1 }}>
    <Link component="button" type="button" onClick={onAdd}>
      {label}
    </Link>
    <Tooltip title={tooltip}>
      <InfoOutlinedIcon fontSize="small" color="action" />
    </Tooltip>
  </Box>
);

interface HostResultRowProps {
  expanded: boolean;
  host: HostEntry;
  isAdded: (type: EntryType, value: string) => boolean;
  onAdd: (type: EntryType, value: string) => void;
  onToggle: () => void;
  tip: { attr: string; value: string } | null;
}

const HostResultRow = ({ host, tip, expanded, isAdded, onToggle, onAdd }: HostResultRowProps) => (
  <Box
    sx={theme => ({
      backgroundColor: expanded ? (theme.palette.mode === 'dark' ? darkBackground[700] : gray[50]) : 'transparent',
      '&:hover': { backgroundColor: theme.palette.mode === 'dark' ? darkBackground[700] : gray[50] }
    })}
  >
    <ButtonBase
      aria-expanded={expanded}
      sx={theme => ({
        alignItems: 'center',
        display: 'flex',
        justifyContent: 'space-between',
        px: 1.5,
        py: 0.5,
        textAlign: 'left',
        width: '100%',
        backgroundColor: expanded ? (theme.palette.mode === 'dark' ? darkBackground[300] : gray[200]) : 'transparent',
        '&:hover': { backgroundColor: theme.palette.mode === 'dark' ? darkBackground[300] : gray[200] }
      })}
      onClick={onToggle}
    >
      <Typography variant="body2" sx={{ fontWeight: 500, wordBreak: 'break-word' }}>
        {host.hostname || host.hostkey}
        {tip && (
          <Typography component="span" variant="caption" color="text.secondary">
            {' '}
            ({tip.attr}: {tip.value})
          </Typography>
        )}
      </Typography>
      {expanded ? <ExpandLessIcon fontSize="small" /> : <ExpandMoreIcon fontSize="small" />}
    </ButtonBase>
    <Collapse in={expanded} unmountOnExit>
      <Box sx={{ py: 0.5 }}>
        <AddRow label={`ID: ${host.hostkey}`} added={isAdded('hostkey', host.hostkey)} onAdd={() => onAdd('hostkey', host.hostkey)} />
        {splitValues(host.ip).map(ip => (
          <AddRow key={ip} label={`IP: ${ip}`} added={isAdded('ip', ip)} onAdd={() => onAdd('ip', ip)} />
        ))}
        {host.hostname && <AddRow label={`Name: ${host.hostname}`} added={isAdded('hostname', host.hostname)} onAdd={() => onAdd('hostname', host.hostname)} />}
        {splitValues(host.mac).map(mac => (
          <AddRow key={mac} label={`MAC: ${mac}`} added={isAdded('mac', mac)} onAdd={() => onAdd('mac', mac)} />
        ))}
      </Box>
    </Collapse>
  </Box>
);

const AddRow = ({ label, added, onAdd }: { added: boolean; label: string; onAdd: () => void }) => (
  <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1, pl: 1.5, py: '3px' }}>
    <Typography variant="body2" sx={{ color: 'text.primary', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
      {label}
    </Typography>
    <Button
      size="small"
      variant="text"
      endIcon={<ArrowForwardIcon />}
      disabled={added}
      sx={theme => ({
        color: theme.palette.mode === 'dark' ? blue[500] : blue[600],
        flexShrink: 0,
        fontWeight: 500,
        minHeight: 0,
        py: 0,
        '&.Mui-disabled': { color: gray[400] },
        '&:hover': { backgroundColor: 'transparent' }
      })}
      onClick={onAdd}
    >
      Add
    </Button>
  </Box>
);

interface SelectedEntriesProps {
  draft: EntriesMap;
  onRemove: (type: EntryType, value: string) => void;
  resolved: ResolvedEntries;
  selectedType: TabType;
}

interface SelectedRow {
  content: React.ReactNode;
  key: string;
  tooltip?: string;
  type: EntryType;
  value: string;
}

// Truncate the long hostkey segment while keeping the trailing ")" visible.
const midEllipsisSx = { display: 'inline-block', maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', verticalAlign: 'bottom', whiteSpace: 'nowrap' } as const;

const selectedRows = (selectedType: TabType, draft: EntriesMap, resolved: ResolvedEntries): SelectedRow[] => {
  if (selectedType === 'hostkey') {
    return (draft.hostkey || []).map(value => ({
      key: value,
      type: 'hostkey' as EntryType,
      value,
      content: (
        <>
          {resolved.hostkey[value]?.hostname ?? ''} (
          <Box component="span" sx={midEllipsisSx}>
            {value}
          </Box>
          )
        </>
      )
    }));
  }
  if (selectedType === 'ip') {
    return [
      ...(draft.ip_mask || []).map(subnet => ({
        key: `mask-${subnet}`,
        type: 'ip_mask' as EntryType,
        value: subnet,
        content: `${subnet} (${pluralize('host', resolved.ip_mask[subnet]?.length ?? 0, true)})`,
        tooltip: ipMaskDescription(subnet, resolved)
      })),
      ...(draft.ip || []).map(ip => ({
        key: `ip-${ip}`,
        type: 'ip' as EntryType,
        value: ip,
        content: `${ip} (${resolved.ip[ip]?.hostname || 'no hostname'})`
      }))
    ];
  }
  if (selectedType === 'hostname') {
    return (draft.hostname || []).map(value => ({ key: value, type: 'hostname' as EntryType, value, content: value }));
  }
  return (draft.mac || []).map(value => ({
    key: value,
    type: 'mac' as EntryType,
    value,
    content: `${value} (${resolved.mac[value]?.hostname ?? ''})`
  }));
};

const SelectedEntries = ({ selectedType, draft, resolved, onRemove }: SelectedEntriesProps) => {
  const rows = selectedRows(selectedType, draft, resolved);

  if (rows.length === 0) {
    return (
      <Typography variant="body2" color="text.secondary" sx={{ py: 2 }}>
        Nothing selected yet.
      </Typography>
    );
  }

  return (
    <Box sx={{ mt: 1 }}>
      {rows.map(row => (
        <Box key={row.key} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1, py: 1 }}>
          <Tooltip title={row.tooltip ?? ''} disableHoverListener={!row.tooltip}>
            <Typography variant="subtitle2" sx={{ minWidth: 0 }}>
              {row.content}
            </Typography>
          </Tooltip>
          <Link
            component="button"
            type="button"
            sx={theme => ({
              color: theme.palette.mode === 'dark' ? red[400] : red[600],
              flexShrink: 0
            })}
            onClick={() => onRemove(row.type, row.value)}
          >
            Remove
          </Link>
        </Box>
      ))}
    </Box>
  );
};
