const { APP_TEMPLATES, TEMPLATE_CATEGORIES, DIFFICULTY_LEVELS } = require('../src/docker/app-templates');

describe('App Templates', () => {
  const templates = Object.values(APP_TEMPLATES);
  const templateIds = Object.keys(APP_TEMPLATES);
  const categoryNames = Object.keys(TEMPLATE_CATEGORIES);

  describe('Template Structure', () => {
    it('has at least 40 templates', () => {
      expect(templates.length).toBeGreaterThanOrEqual(40);
    });

    it('every template has required fields: name, description, icon, category', () => {
      for (const tmpl of templates) {
        expect(tmpl).toHaveProperty('name');
        expect(tmpl).toHaveProperty('description');
        expect(tmpl).toHaveProperty('icon');
        expect(tmpl).toHaveProperty('category');
        expect(typeof tmpl.name).toBe('string');
        expect(tmpl.name.length).toBeGreaterThan(0);
        expect(typeof tmpl.description).toBe('string');
      }
    });

    it('every Docker-based template has docker config with image', () => {
      for (const id of templateIds) {
        const tmpl = APP_TEMPLATES[id];
        if (!tmpl.docker) continue; // Skip static sites and dashboard widgets
        expect(tmpl.docker).toHaveProperty('image');
        expect(typeof tmpl.docker.image).toBe('string');
        expect(tmpl.docker.image.length).toBeGreaterThan(0);
      }
    });

    it('every template has subdomain property', () => {
      for (const id of templateIds) {
        const tmpl = APP_TEMPLATES[id];
        expect(tmpl).toHaveProperty('subdomain');
        // subdomain can be null for widgets
        if (tmpl.subdomain !== null) {
          expect(typeof tmpl.subdomain).toBe('string');
        }
      }
    });

    it('all Docker-based templates have valid defaultPorts (1-65535)', () => {
      for (const id of templateIds) {
        const tmpl = APP_TEMPLATES[id];
        if (!tmpl.docker) continue; // Skip non-Docker templates
        const port = tmpl.defaultPort;
        expect(port).toBeGreaterThanOrEqual(1);
        expect(port).toBeLessThanOrEqual(65535);
      }
    });

    it('all category values are in TEMPLATE_CATEGORIES', () => {
      for (const tmpl of templates) {
        expect(categoryNames).toContain(tmpl.category);
      }
    });

    it('Docker images have no shell injection characters', () => {
      const dangerous = [';', '&', '|', '`', '$', '\n'];
      for (const id of templateIds) {
        const tmpl = APP_TEMPLATES[id];
        if (!tmpl.docker) continue;
        const image = tmpl.docker.image;
        for (const char of dangerous) {
          expect(image).not.toContain(char);
        }
      }
    });
  });

  describe('TEMPLATE_CATEGORIES', () => {
    it('is a non-empty object with category entries', () => {
      expect(typeof TEMPLATE_CATEGORIES).toBe('object');
      expect(TEMPLATE_CATEGORIES).not.toBeNull();
      expect(categoryNames.length).toBeGreaterThan(0);
    });

    it('each category has icon and color', () => {
      for (const name of categoryNames) {
        const cat = TEMPLATE_CATEGORIES[name];
        expect(cat).toHaveProperty('icon');
        expect(cat).toHaveProperty('color');
        expect(typeof cat.color).toBe('string');
      }
    });
  });

  describe('DIFFICULTY_LEVELS', () => {
    it('is a non-empty object with difficulty entries', () => {
      const levels = Object.keys(DIFFICULTY_LEVELS);
      expect(levels.length).toBeGreaterThan(0);
    });

    it('each level has color and description', () => {
      for (const [name, level] of Object.entries(DIFFICULTY_LEVELS)) {
        expect(level).toHaveProperty('color');
        expect(level).toHaveProperty('description');
        expect(typeof level.color).toBe('string');
        expect(typeof level.description).toBe('string');
      }
    });

    it('includes Easy, Intermediate, and Advanced levels', () => {
      expect(DIFFICULTY_LEVELS).toHaveProperty('Easy');
      expect(DIFFICULTY_LEVELS).toHaveProperty('Intermediate');
      expect(DIFFICULTY_LEVELS).toHaveProperty('Advanced');
    });
  });

  describe('Specific Templates', () => {
    it('plex template has PLEX_CLAIM as empty string', () => {
      const plex = APP_TEMPLATES.plex;
      expect(plex).toBeDefined();
      expect(plex.docker.environment).toHaveProperty('PLEX_CLAIM');
      expect(plex.docker.environment.PLEX_CLAIM).toBe('');
    });

    it('jellyfin template exists with correct default port', () => {
      const jf = APP_TEMPLATES.jellyfin;
      expect(jf).toBeDefined();
      expect(jf.defaultPort).toBe(8096);
    });

    it('radarr template exists with correct default port', () => {
      const radarr = APP_TEMPLATES.radarr;
      expect(radarr).toBeDefined();
      expect(radarr.defaultPort).toBe(7878);
    });

    it('sonarr template exists with correct default port', () => {
      const sonarr = APP_TEMPLATES.sonarr;
      expect(sonarr).toBeDefined();
      expect(sonarr.defaultPort).toBe(8989);
    });

    it('prowlarr template exists with correct default port', () => {
      const prowlarr = APP_TEMPLATES.prowlarr;
      expect(prowlarr).toBeDefined();
      expect(prowlarr.defaultPort).toBe(9696);
    });

    it('DashCA is a static site without docker config', () => {
      const dashca = APP_TEMPLATES.dashca;
      if (dashca) {
        expect(dashca.isStaticSite).toBe(true);
        expect(dashca.docker).toBeUndefined();
      }
    });
  });

  describe('Template Ports', () => {
    it('all templates with docker.ports have valid port mappings', () => {
      // Ports use template syntax like "{{PORT}}:32400" or "{{PORT}}:32400/tcp"
      const portPattern = /^(\{\{PORT\}\}|\d+):(\d+)(\/[a-z]+)?$/;
      for (const id of templateIds) {
        const tmpl = APP_TEMPLATES[id];
        if (!tmpl.docker || !tmpl.docker.ports) continue;
        expect(Array.isArray(tmpl.docker.ports)).toBe(true);
        for (const port of tmpl.docker.ports) {
          expect(typeof port).toBe('string');
          expect(port).toMatch(portPattern);
        }
      }
    });

    it('no two templates share the same default port (prevent conflicts)', () => {
      const portMap = new Map();
      for (const id of templateIds) {
        const port = APP_TEMPLATES[id].defaultPort;
        if (port !== null) {
          portMap.set(port, id);
        }
      }
      // At minimum, we should have more unique ports than 30% of templates
      expect(portMap.size).toBeGreaterThan(templateIds.length * 0.3);
    });
  });
});
