import { describe, it, expect } from 'vitest';
import {
  toWildcardQuery,
  buildJMAPFilter,
  isFilterEmpty,
  activeFilterCount,
  DEFAULT_SEARCH_FILTERS,
} from '../jmap/search-utils';
import type { SearchFilters } from '../jmap/search-utils';

// ---------------------------------------------------------------------------
// toWildcardQuery
// ---------------------------------------------------------------------------
describe('toWildcardQuery', () => {
  it('appends * to a single word', () => {
    expect(toWildcardQuery('pri')).toBe('pri*');
  });

  it('appends * to every word in a multi-word query', () => {
    expect(toWildcardQuery('hello world')).toBe('hello* world*');
  });

  it('handles single-character queries', () => {
    expect(toWildcardQuery('a')).toBe('a*');
  });

  it('handles two-character queries', () => {
    expect(toWildcardQuery('pr')).toBe('pr*');
  });

  it('handles long words', () => {
    expect(toWildcardQuery('internationalization')).toBe('internationalization*');
  });

  it('does not double-append * if already present', () => {
    expect(toWildcardQuery('hello*')).toBe('hello*');
    expect(toWildcardQuery('hello* world')).toBe('hello* world*');
  });

  it('preserves quoted phrases ending with "', () => {
    expect(toWildcardQuery('"hello world"')).toBe('"hello* world"');
  });

  it('trims leading/trailing whitespace', () => {
    expect(toWildcardQuery('  hello  ')).toBe('hello*');
  });

  it('collapses multiple spaces between words', () => {
    expect(toWildcardQuery('hello    world')).toBe('hello* world*');
  });

  it('returns empty string for empty input', () => {
    expect(toWildcardQuery('')).toBe('');
  });

  it('returns empty string for whitespace-only input', () => {
    expect(toWildcardQuery('   ')).toBe('');
  });

  it('handles mixed words with and without wildcards', () => {
    expect(toWildcardQuery('foo* bar baz*')).toBe('foo* bar* baz*');
  });
});

// ---------------------------------------------------------------------------
// buildJMAPFilter
// ---------------------------------------------------------------------------
describe('buildJMAPFilter', () => {
  const emptyFilters: SearchFilters = { ...DEFAULT_SEARCH_FILTERS };

  // -- text query ----------------------------------------------------------
  describe('text query', () => {
    it('builds a text filter with wildcard from textQuery alone', () => {
      const result = buildJMAPFilter('pri', emptyFilters);
      expect(result).toEqual({ text: 'pri*' });
    });

    it('wildcards each word in multi-word text query', () => {
      const result = buildJMAPFilter('hello world', emptyFilters);
      expect(result).toEqual({ text: 'hello* world*' });
    });

    it('returns empty object when no query and no filters', () => {
      const result = buildJMAPFilter('', emptyFilters);
      expect(result).toEqual({});
    });
  });

  // -- individual field filters -------------------------------------------
  describe('individual field filters', () => {
    it('builds a from filter (no wildcard)', () => {
      const filters: SearchFilters = { ...emptyFilters, from: 'alice' };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ from: 'alice' });
    });

    it('builds a to filter (no wildcard)', () => {
      const filters: SearchFilters = { ...emptyFilters, to: 'bob@example.com' };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ to: 'bob@example.com' });
    });

    it('builds a subject filter (no wildcard)', () => {
      const filters: SearchFilters = { ...emptyFilters, subject: 'meeting' };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ subject: 'meeting' });
    });

    it('builds a body filter (no wildcard)', () => {
      const filters: SearchFilters = { ...emptyFilters, body: 'payment' };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ body: 'payment' });
    });
  });

  // -- boolean / keyword filters ------------------------------------------
  describe('boolean and keyword filters', () => {
    it('adds hasAttachment: true', () => {
      const filters: SearchFilters = { ...emptyFilters, hasAttachment: true };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ hasAttachment: true });
    });

    it('adds hasAttachment: false', () => {
      const filters: SearchFilters = { ...emptyFilters, hasAttachment: false };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ hasAttachment: false });
    });

    it('adds notKeyword $seen for isUnread=true', () => {
      const filters: SearchFilters = { ...emptyFilters, isUnread: true };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ notKeyword: '$seen' });
    });

    it('adds hasKeyword $seen for isUnread=false', () => {
      const filters: SearchFilters = { ...emptyFilters, isUnread: false };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ hasKeyword: '$seen' });
    });

    it('adds hasKeyword $flagged for isStarred=true', () => {
      const filters: SearchFilters = { ...emptyFilters, isStarred: true };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ hasKeyword: '$flagged' });
    });

    it('adds notKeyword $flagged for isStarred=false', () => {
      const filters: SearchFilters = { ...emptyFilters, isStarred: false };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({ notKeyword: '$flagged' });
    });
  });

  // -- date filters -------------------------------------------------------
  describe('date filters', () => {
    it('adds after condition for dateAfter', () => {
      const filters: SearchFilters = { ...emptyFilters, dateAfter: '2024-06-01' };
      const result = buildJMAPFilter('', filters);
      expect(result).toHaveProperty('after');
      expect(new Date(result.after as string).toISOString()).toContain('2024-06-01');
    });

    it('adds before condition for dateBefore (end of day)', () => {
      const filters: SearchFilters = { ...emptyFilters, dateBefore: '2024-12-31' };
      const result = buildJMAPFilter('', filters);
      expect(result).toHaveProperty('before');
      const d = new Date(result.before as string);
      expect(d.getHours()).toBe(23);
      expect(d.getMinutes()).toBe(59);
    });

    it('ignores invalid dateAfter', () => {
      const filters: SearchFilters = { ...emptyFilters, dateAfter: 'not-a-date' };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({});
    });

    it('ignores invalid dateBefore', () => {
      const filters: SearchFilters = { ...emptyFilters, dateBefore: 'nope' };
      const result = buildJMAPFilter('', filters);
      expect(result).toEqual({});
    });
  });

  // -- mailbox filter ------------------------------------------------------
  describe('mailbox filter', () => {
    it('returns inMailbox filter when only mailboxId provided', () => {
      const result = buildJMAPFilter('', emptyFilters, 'inbox-1');
      expect(result).toEqual({ inMailbox: 'inbox-1' });
    });

    it('wraps in AND when mailboxId combined with text query', () => {
      const result = buildJMAPFilter('pri', emptyFilters, 'inbox-1');
      expect(result).toEqual({
        operator: 'AND',
        conditions: [
          { text: 'pri*' },
          { inMailbox: 'inbox-1' },
        ],
      });
    });
  });

  // -- combinations --------------------------------------------------------
  describe('combined filters', () => {
    it('ANDs text query with from filter', () => {
      const filters: SearchFilters = { ...emptyFilters, from: 'alice' };
      const result = buildJMAPFilter('urgent', filters);
      expect(result).toEqual({
        operator: 'AND',
        conditions: [
          { text: 'urgent*' },
          { from: 'alice' },
        ],
      });
    });

    it('ANDs text query, from, subject, hasAttachment, and mailbox', () => {
      const filters: SearchFilters = {
        ...emptyFilters,
        from: 'ceo@corp.com',
        subject: 'quarterly',
        hasAttachment: true,
      };
      const result = buildJMAPFilter('report', filters, 'mb-42');
      expect(result).toEqual({
        operator: 'AND',
        conditions: [
          { text: 'report*' },
          { from: 'ceo@corp.com' },
          { subject: 'quarterly' },
          { hasAttachment: true },
          { inMailbox: 'mb-42' },
        ],
      });
    });

    it('ANDs all possible filters at once', () => {
      const filters: SearchFilters = {
        from: 'alice',
        to: 'bob',
        subject: 'invoice',
        body: 'payment',
        hasAttachment: true,
        dateAfter: '2024-01-01',
        dateBefore: '2024-12-31',
        isUnread: true,
        isStarred: true,
      };
      const result = buildJMAPFilter('money', filters, 'mb-1') as {
        operator: string;
        conditions: Record<string, unknown>[];
      };
      expect(result.operator).toBe('AND');
      expect(result.conditions).toContainEqual({ text: 'money*' });
      expect(result.conditions).toContainEqual({ from: 'alice' });
      expect(result.conditions).toContainEqual({ to: 'bob' });
      expect(result.conditions).toContainEqual({ subject: 'invoice' });
      expect(result.conditions).toContainEqual({ body: 'payment' });
      expect(result.conditions).toContainEqual({ hasAttachment: true });
      expect(result.conditions).toContainEqual({ notKeyword: '$seen' });
      expect(result.conditions).toContainEqual({ hasKeyword: '$flagged' });
      expect(result.conditions).toContainEqual({ inMailbox: 'mb-1' });
      // dateAfter and dateBefore produce after/before ISO strings
      expect(result.conditions.some(c => 'after' in c)).toBe(true);
      expect(result.conditions.some(c => 'before' in c)).toBe(true);
    });
  });
});

// ---------------------------------------------------------------------------
// isFilterEmpty
// ---------------------------------------------------------------------------
describe('isFilterEmpty', () => {
  it('returns true for default filters', () => {
    expect(isFilterEmpty(DEFAULT_SEARCH_FILTERS)).toBe(true);
  });

  it('returns false when from is set', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, from: 'alice' })).toBe(false);
  });

  it('returns false when to is set', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, to: 'bob' })).toBe(false);
  });

  it('returns false when subject is set', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, subject: 'hello' })).toBe(false);
  });

  it('returns false when body is set', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, body: 'world' })).toBe(false);
  });

  it('returns false when hasAttachment is true', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, hasAttachment: true })).toBe(false);
  });

  it('returns false when hasAttachment is false', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, hasAttachment: false })).toBe(false);
  });

  it('returns false when dateAfter is set', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, dateAfter: '2024-01-01' })).toBe(false);
  });

  it('returns false when dateBefore is set', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, dateBefore: '2024-12-31' })).toBe(false);
  });

  it('returns false when isUnread is set', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, isUnread: true })).toBe(false);
  });

  it('returns false when isStarred is set', () => {
    expect(isFilterEmpty({ ...DEFAULT_SEARCH_FILTERS, isStarred: false })).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// activeFilterCount
// ---------------------------------------------------------------------------
describe('activeFilterCount', () => {
  it('returns 0 for default filters', () => {
    expect(activeFilterCount(DEFAULT_SEARCH_FILTERS)).toBe(0);
  });

  it('counts each active text field', () => {
    expect(activeFilterCount({ ...DEFAULT_SEARCH_FILTERS, from: 'a', to: 'b' })).toBe(2);
  });

  it('counts boolean filters', () => {
    expect(activeFilterCount({
      ...DEFAULT_SEARCH_FILTERS,
      hasAttachment: true,
      isUnread: false,
      isStarred: true,
    })).toBe(3);
  });

  it('counts date filters', () => {
    expect(activeFilterCount({
      ...DEFAULT_SEARCH_FILTERS,
      dateAfter: '2024-01-01',
      dateBefore: '2024-12-31',
    })).toBe(2);
  });

  it('counts all 9 filters when all active', () => {
    expect(activeFilterCount({
      from: 'a',
      to: 'b',
      subject: 'c',
      body: 'd',
      hasAttachment: true,
      dateAfter: '2024-01-01',
      dateBefore: '2024-12-31',
      isUnread: true,
      isStarred: true,
    })).toBe(9);
  });
});
