import { DOCUMENT } from '@angular/common';
import { EnvironmentInjector, createEnvironmentInjector } from '@angular/core';

import { DEFAULT_THEME_JSON, createDefaultThemeDocument } from '../../domain/logic/theme-defaults.logic';
import { validateThemeDocument } from '../../domain/logic/theme-validation.logic';
import { ThemeService } from './theme.service';

describe('ThemeService theme application', () => {
  let injector: EnvironmentInjector;
  let styleElements: TestStyleElement[];
  let service: ThemeService;

  beforeEach(() => {
    installLocalStorageMock();
    styleElements = [];
    injector = createEnvironmentInjector([
      ThemeService,
      {
        provide: DOCUMENT,
        useValue: createDocumentStub(styleElements)
      }
    ]);

    service = injector.get(ThemeService);
    service.initialize();
  });

  afterEach(() => {
    injector.destroy();
    vi.unstubAllGlobals();
  });

  it('uses the compact Toju dark theme as the built-in default JSON', () => {
    expect(JSON.parse(DEFAULT_THEME_JSON) as unknown).toEqual({
      meta: {
        name: 'Toju Default Dark',
        version: '2.0.0',
        description: 'Built-in dark glass theme for the full Toju app shell.'
      },
      tokens: {
        colors: {
          background: '224 28% 7%',
          foreground: '210 40% 96%',
          card: '224 25% 10%',
          cardForeground: '210 40% 96%',
          popover: '224 26% 9%',
          popoverForeground: '210 40% 96%',
          primary: '193 95% 68%',
          primaryForeground: '222 47% 11%',
          secondary: '223 19% 16%',
          secondaryForeground: '210 40% 96%',
          muted: '223 18% 14%',
          mutedForeground: '215 20% 70%',
          accent: '218 22% 18%',
          accentForeground: '210 40% 98%',
          destructive: '0 72% 55%',
          destructiveForeground: '0 0% 100%',
          border: '222 18% 22%',
          input: '222 18% 22%',
          ring: '193 95% 68%',
          railBackground: '226 33% 8%',
          workspaceBackground: '224 30% 9%',
          panelBackground: '224 24% 11%',
          panelBackgroundAlt: '222 22% 13%',
          titleBarBackground: '226 34% 7%',
          surfaceHighlight: '193 95% 68%',
          surfaceHighlightAlt: '261 82% 72%'
        },
        spacing: {},
        radii: {
          radius: '0.875rem',
          surface: '1.35rem',
          pill: '999px'
        },
        effects: {
          panelShadow: '0 24px 60px rgba(0, 0, 0, 0.42)',
          softShadow: '0 14px 36px rgba(0, 0, 0, 0.28)',
          glassBlur: 'blur(18px) saturate(135%)'
        }
      },
      layout: {
        serversRail: {
          container: 'appShell',
          grid: { x: 0, y: 0, w: 1, h: 1 }
        },
        appWorkspace: {
          container: 'appShell',
          grid: { x: 1, y: 0, w: 19, h: 1 }
        },
        chatRoomChannelsPanel: {
          container: 'roomLayout',
          grid: { x: 0, y: 0, w: 4, h: 12 }
        },
        chatRoomMainPanel: {
          container: 'roomLayout',
          grid: { x: 4, y: 0, w: 12, h: 12 }
        },
        chatRoomMembersPanel: {
          container: 'roomLayout',
          grid: { x: 16, y: 0, w: 4, h: 12 }
        }
      }
    });
  });

  it('applies a JSON theme with tokens, layout, backgrounds, effects, metadata, and animations', () => {
    const theme = createCompleteThemeDocument();
    const loaded = service.loadThemeText(JSON.stringify(theme), 'apply', 'Theme applied.', 'complete JSON theme');

    expect(loaded).toBe(true);
    expect(service.activeThemeName()).toBe('Complete Theme Fixture');
    expect(service.getTextOverride('titleBar')).toBe('MetoYou Lab');
    expect(service.getIcon('titleBar')).toBe('MT');
    expect(service.getLink('titleBar')).toBe('https://example.com/theme');
    expect(service.getAnimationClass('titleBar')).toBe('themePulse');

    expect(service.getHostStyles('appRoot')).toMatchObject({
      '--background': '210 22% 8%',
      '--theme-spacing-panel-gap': '14px',
      '--radius': '1rem',
      '--theme-radius-surface': '1.25rem',
      '--theme-effect-glass-blur': 'blur(20px) saturate(140%)'
    });

    expect(service.getHostStyles('titleBar')).toMatchObject({
      width: 'min(100%, 64rem)',
      height: '4rem',
      minWidth: '18rem',
      minHeight: '3rem',
      maxWidth: '72rem',
      maxHeight: '5rem',
      position: 'sticky',
      top: '0',
      right: '0',
      bottom: 'auto',
      left: '0',
      padding: '0.75rem 1rem',
      margin: '0 auto',
      border: '1px solid hsl(var(--border) / 0.75)',
      borderRadius: '1rem',
      backgroundColor: 'rgba(7, 11, 20, 0.88)',
      color: '#f8fafc',
      backgroundImage: 'linear-gradient(90deg, rgba(14, 165, 233, 0.28), transparent), url("/themes/city.png")',
      backgroundSize: 'cover',
      backgroundPosition: 'center',
      backgroundRepeat: 'no-repeat',
      boxShadow: '0 18px 50px rgba(0, 0, 0, 0.35)',
      backdropFilter: 'var(--theme-effect-glass-blur)',
      opacity: '0.82'
    });

    expect(service.getLayoutContainerStyles('dmLayout')).toMatchObject({
      display: 'grid',
      gridTemplateColumns: 'repeat(4, 4.25rem) repeat(16, minmax(0, 1fr))'
    });

    expect(service.getLayoutItemStyles('dmChatPanel')).toMatchObject({
      gridColumn: '7 / span 14',
      gridRow: '2 / span 10'
    });

    const animationStylesheet = styleElements.find((styleElement) => styleElement.textContent?.includes('@keyframes themePulse'));

    expect(animationStylesheet?.textContent).toContain('@keyframes themePulse');
    expect(animationStylesheet?.textContent).toContain('animation-direction: alternate-reverse;');
    expect(animationStylesheet?.textContent).toContain('transform: scale(1);');
  });

  it('applies raw CSS from the theme JSON', () => {
    const loaded = service.loadThemeText(
      JSON.stringify({
        meta: {
          name: 'CSS Theme',
          version: '1.0.0'
        },
        css: '.theme-css-probe { color: hsl(var(--primary)); }'
      }),
      'apply',
      'Theme applied.',
      'CSS JSON theme'
    );

    expect(loaded).toBe(true);
    expect(styleElements.some((styleElement) => styleElement.textContent === '.theme-css-probe { color: hsl(var(--primary)); }')).toBe(true);
    expect(service.activeThemeText()).toContain('"css"');
  });

  it('applies a CSS-only theme over the default JSON theme', () => {
    const applied = service.applyCssOnlyTheme('.css-only-theme { background: hsl(var(--background)); }');

    expect(applied).toBe(true);
    expect(service.activeThemeName()).toBe('Toju Default Dark');
    expect(service.getLayoutItemStyles('dmChatPanel')).toMatchObject({
      gridColumn: '5 / span 16',
      gridRow: '1 / span 12'
    });

    expect(service.getHostStyles('appRoot')).toMatchObject({
      '--background': '224 28% 7%'
    });

    expect(styleElements.some((styleElement) => styleElement.textContent === '.css-only-theme { background: hsl(var(--background)); }')).toBe(true);
  });

  it('applies CSS over the current JSON draft theme', () => {
    const draftTheme = createDefaultThemeDocument();

    draftTheme.meta = {
      name: 'Draft Base Theme',
      version: '1.0.0'
    };

    draftTheme.tokens.colors['background'] = '200 50% 10%';
    draftTheme.layout['dmChatPanel'] = {
      container: 'dmLayout',
      grid: { x: 6, y: 0, w: 14, h: 12 }
    };

    const loadedDraft = service.loadThemeText(JSON.stringify(draftTheme), 'draft', 'Draft loaded.', 'draft JSON theme');
    const applied = service.applyCssOnlyTheme('.draft-base-theme { color: hsl(var(--foreground)); }');

    expect(loadedDraft).toBe(true);
    expect(applied).toBe(true);
    expect(service.activeThemeName()).toBe('Draft Base Theme');
    expect(service.getHostStyles('appRoot')).toMatchObject({
      '--background': '200 50% 10%'
    });

    expect(service.getLayoutItemStyles('dmChatPanel')).toMatchObject({
      gridColumn: '7 / span 14',
      gridRow: '1 / span 12'
    });

    expect(styleElements.some((styleElement) => styleElement.textContent === '.draft-base-theme { color: hsl(var(--foreground)); }')).toBe(true);
  });

  it('builds exportable JSON with CSS over the current draft without applying it', () => {
    const draftTheme = createDefaultThemeDocument();

    draftTheme.meta = {
      name: 'Export Base Theme',
      version: '1.0.0'
    };

    draftTheme.tokens.colors['background'] = '180 40% 12%';

    const loadedDraft = service.loadThemeText(JSON.stringify(draftTheme), 'draft', 'Draft loaded.', 'draft JSON theme');
    const exportText = service.buildDraftTextWithCss('.export-base-theme { color: hsl(var(--foreground)); }');

    expect(loadedDraft).toBe(true);
    expect(exportText).not.toBeNull();
    expect(JSON.parse(exportText ?? '{}')).toMatchObject({
      meta: {
        name: 'Export Base Theme'
      },
      css: '.export-base-theme { color: hsl(var(--foreground)); }',
      tokens: {
        colors: {
          background: '180 40% 12%'
        }
      }
    });

    expect(service.activeThemeName()).toBe('Toju Default Dark');
  });

  it('validates the dedicated DM workspace layout container', () => {
    const theme = createDefaultThemeDocument();

    theme.layout['dmConversationsPanel'] = {
      container: 'dmLayout',
      grid: { x: 0, y: 0, w: 5, h: 12 }
    };

    theme.layout['dmChatPanel'] = {
      container: 'dmLayout',
      grid: { x: 5, y: 0, w: 15, h: 12 }
    };

    const result = validateThemeDocument(theme);

    expect(result.valid).toBe(true);
    expect(result.value?.layout['dmChatPanel'].container).toBe('dmLayout');
  });

  it('allows compact JSON themes with omitted element sections', () => {
    const loaded = service.loadThemeText(
      JSON.stringify({
        meta: {
          name: 'Compact Theme',
          version: '1.0.0'
        },
        elements: {
          titleBar: {
            backgroundImage: '/themes/city.png',
            backgroundSize: 'cover'
          }
        }
      }),
      'apply',
      'Theme applied.',
      'compact JSON theme'
    );

    expect(loaded).toBe(true);
    expect(service.getHostStyles('titleBar')).toMatchObject({
      backgroundImage: 'url("/themes/city.png")',
      backgroundSize: 'cover'
    });

    expect(service.getHostStyles('voiceWorkspace')).toEqual({});
    expect(service.activeThemeText()).not.toContain('voiceWorkspace');
  });

  it('omits empty element stubs when formatting themes', () => {
    const loaded = service.loadThemeText(
      JSON.stringify({
        meta: {
          name: 'Theme With Empty Elements',
          version: '1.0.0'
        },
        elements: {
          titleBar: {},
          voiceWorkspace: {}
        }
      }),
      'apply',
      'Theme applied.',
      'theme with empty elements'
    );

    expect(loaded).toBe(true);
    expect(service.activeThemeText()).not.toContain('"elements"');
  });

  it('keeps only non-empty element overrides when formatting themes', () => {
    const loaded = service.loadThemeText(
      JSON.stringify({
        meta: {
          name: 'Theme With Mixed Elements',
          version: '1.0.0'
        },
        elements: {
          titleBar: {},
          chatRoomMainPanel: {
            backgroundImage: '/themes/city.png'
          }
        }
      }),
      'apply',
      'Theme applied.',
      'theme with mixed elements'
    );
    const activeTheme = JSON.parse(service.activeThemeText()) as { elements?: Record<string, unknown> };

    expect(loaded).toBe(true);
    expect(activeTheme.elements).toEqual({
      chatRoomMainPanel: {
        backgroundImage: '/themes/city.png'
      }
    });
  });

  it('allows removing the entire elements section', () => {
    const loaded = service.loadThemeText(
      JSON.stringify({
        meta: {
          name: 'No Elements Theme',
          version: '1.0.0'
        }
      }),
      'apply',
      'Theme applied.',
      'compact JSON theme'
    );

    expect(loaded).toBe(true);
    expect(service.getHostStyles('titleBar')).toEqual({});
    expect(service.activeThemeText()).not.toContain('"elements"');
  });
});

interface TestStyleElement {
  textContent: string | null;
  setAttribute(name: string, value: string): void;
}

function createCompleteThemeDocument() {
  const theme = createDefaultThemeDocument();

  theme.meta = {
    name: 'Complete Theme Fixture',
    version: '9.9.9',
    description: 'Exercises every supported applied theme surface.'
  };

  theme.tokens.colors['background'] = '210 22% 8%';
  theme.tokens.spacing['panelGap'] = '14px';
  theme.tokens.radii['radius'] = '1rem';
  theme.tokens.radii['surface'] = '1.25rem';
  theme.tokens.effects['glassBlur'] = 'blur(20px) saturate(140%)';
  theme.layout['dmChatPanel'] = {
    container: 'dmLayout',
    grid: { x: 6, y: 1, w: 14, h: 10 }
  };

  theme.elements['titleBar'] = {
    width: 'min(100%, 64rem)',
    height: '4rem',
    minWidth: '18rem',
    minHeight: '3rem',
    maxWidth: '72rem',
    maxHeight: '5rem',
    position: 'sticky',
    top: '0',
    right: '0',
    bottom: 'auto',
    left: '0',
    opacity: 0.82,
    padding: '0.75rem 1rem',
    margin: '0 auto',
    border: '1px solid hsl(var(--border) / 0.75)',
    borderRadius: '1rem',
    backgroundColor: 'rgba(7, 11, 20, 0.88)',
    color: '#f8fafc',
    backgroundImage: '/themes/city.png',
    backgroundSize: 'cover',
    backgroundPosition: 'center',
    backgroundRepeat: 'no-repeat',
    gradient: 'linear-gradient(90deg, rgba(14, 165, 233, 0.28), transparent)',
    boxShadow: '0 18px 50px rgba(0, 0, 0, 0.35)',
    backdropFilter: 'var(--theme-effect-glass-blur)',
    icon: 'MT',
    textOverride: 'MetoYou Lab',
    link: 'https://example.com/theme',
    animationClass: 'themePulse'
  };

  theme.animations['themePulse'] = {
    duration: '600ms',
    easing: 'cubic-bezier(0.16, 1, 0.3, 1)',
    delay: '50ms',
    iterationCount: 'infinite',
    fillMode: 'both',
    direction: 'alternate-reverse',
    keyframes: {
      from: {
        opacity: 0,
        transform: 'scale(0.98)'
      },
      to: {
        opacity: 1,
        transform: 'scale(1)'
      }
    }
  };

  return theme;
}

function createDocumentStub(styleElements: TestStyleElement[]): Document {
  return {
    createElement: () => {
      const styleElement: TestStyleElement = {
        textContent: null,
        setAttribute: () => undefined
      };

      return styleElement;
    },
    head: {
      appendChild: (styleElement: TestStyleElement) => {
        styleElements.push(styleElement);
        return styleElement;
      }
    }
  } as unknown as Document;
}

function installLocalStorageMock(): void {
  const values = new Map<string, string>();

  vi.stubGlobal('localStorage', {
    getItem: (key: string) => values.get(key) ?? null,
    setItem: (key: string, value: string) => values.set(key, value),
    removeItem: (key: string) => values.delete(key),
    clear: () => values.clear()
  });
}
