import {
  extractStyleBlocks,
  isValidEmail,
  isValidHexColor,
  isValidHttpUrl,
  normalizeRoute,
  validateAgainstSchemaLite,
  validateCss,
  validateHtml,
  validateJson,
} from './primitives';

describe('validation primitives', () => {
  describe('isValidHexColor', () => {
    it('accepts 3- and 6-digit hex', () => {
      expect(isValidHexColor('#fff')).toBeTrue();
      expect(isValidHexColor('#497671')).toBeTrue();
    });
    it('rejects non-hex', () => {
      expect(isValidHexColor('#zzz')).toBeFalse();
      expect(isValidHexColor('497671')).toBeFalse();
      expect(isValidHexColor('rgb(0,0,0)')).toBeFalse();
    });
  });

  describe('isValidHttpUrl', () => {
    it('accepts http/https', () => {
      expect(isValidHttpUrl('https://dexar.market')).toBeTrue();
      expect(isValidHttpUrl('http://localhost:4200')).toBeTrue();
    });
    it('rejects other schemes and bare strings', () => {
      expect(isValidHttpUrl('ftp://x')).toBeFalse();
      expect(isValidHttpUrl('dexar.market')).toBeFalse();
    });
  });

  describe('isValidEmail', () => {
    it('accepts a normal address and rejects malformed', () => {
      expect(isValidEmail('sales@dexar.market')).toBeTrue();
      expect(isValidEmail('sales@dexar')).toBeFalse();
      expect(isValidEmail('nope')).toBeFalse();
    });
  });

  describe('validateJson', () => {
    it('accepts valid JSON', () => {
      expect(validateJson('{"a":1}').ok).toBeTrue();
    });
    it('rejects malformed and empty JSON', () => {
      expect(validateJson('{a:1}').ok).toBeFalse();
      expect(validateJson('   ').ok).toBeFalse();
    });
  });

  describe('validateCss', () => {
    it('accepts balanced rules and ignores comments', () => {
      expect(validateCss('.a{color:red} /* x */ .b{color:blue}').ok).toBeTrue();
    });
    it('rejects unbalanced braces', () => {
      expect(validateCss('.a{color:red').ok).toBeFalse();
      expect(validateCss('.a}').ok).toBeFalse();
    });
  });

  describe('extractStyleBlocks', () => {
    it('pulls the CSS text out of every style tag', () => {
      const html = '<p>hi</p><style>.a{color:red}</style><style> .b{} </style>';
      expect(extractStyleBlocks(html)).toEqual(['.a{color:red}', ' .b{} ']);
    });
    it('returns an empty array when there are no style tags', () => {
      expect(extractStyleBlocks('<p>plain</p>')).toEqual([]);
    });
  });

  describe('normalizeRoute', () => {
    it('trims, strips wrapping slashes and lowercases', () => {
      expect(normalizeRoute('/About/')).toBe('about');
      expect(normalizeRoute('  Contact ')).toBe('contact');
      expect(normalizeRoute('/')).toBe('');
    });
  });

  describe('validateAgainstSchemaLite', () => {
    const schema = {
      type: 'object' as const,
      properties: { title: { type: 'string' }, count: { type: 'number' } },
      required: ['title'],
    };

    it('accepts a value matching required fields and property types', () => {
      expect(validateAgainstSchemaLite({ title: 'Hero', count: 3 }, schema)).toEqual([]);
    });

    it('flags a missing required property', () => {
      const errors = validateAgainstSchemaLite({ count: 3 }, schema);
      expect(errors.some(e => e.includes('title'))).toBeTrue();
    });

    it('flags a property with the wrong type', () => {
      const errors = validateAgainstSchemaLite({ title: 'Hero', count: 'three' }, schema);
      expect(errors.some(e => e.includes('count'))).toBeTrue();
    });

    it('ignores properties not declared in the schema', () => {
      expect(validateAgainstSchemaLite({ title: 'Hero', extra: 'ignored' }, schema)).toEqual([]);
    });

    it('ignores an undeclared-optional property that is simply absent', () => {
      expect(validateAgainstSchemaLite({ title: 'Hero' }, schema)).toEqual([]);
    });
  });

  describe('validateHtml', () => {
    it('accepts well-formed markup with nested tags', () => {
      expect(validateHtml('<p>Hello <strong>world</strong></p><ul><li>one</li></ul>').ok).toBeTrue();
    });
    it('accepts void elements without a closing tag', () => {
      expect(validateHtml('<p>Line<br>break</p><hr><img src="x.png">').ok).toBeTrue();
    });
    it('accepts self-closing tags', () => {
      expect(validateHtml('<div><span/></div>').ok).toBeTrue();
    });
    it('ignores comments', () => {
      expect(validateHtml('<p>hi</p><!-- <div> unclosed inside a comment -->').ok).toBeTrue();
    });
    it('rejects an unclosed tag', () => {
      const result = validateHtml('<p>Hello <strong>world</p>');
      expect(result.ok).toBeFalse();
      expect(result.error).toContain('strong');
      expect(result.error).toContain('p');
    });
    it('rejects a stray closing tag', () => {
      const result = validateHtml('<p>Hello</p></div>');
      expect(result.ok).toBeFalse();
      expect(result.error).toContain('div');
    });
    it('accepts empty input', () => {
      expect(validateHtml('').ok).toBeTrue();
    });
  });
});
