import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { Subject, of, throwError } from 'rxjs';
import { TagsComponent } from './tags.component';
import { TagService, TagListResponse } from '../services/tag.service';
import { routes } from '../app.routes';

const TAGS_PAGE = (items: { name: string; image_count: number }[]): TagListResponse => ({
  items: items.map((t, i) => ({ id: String(i), ...t })),
  total: items.length,
  limit: 500,
  offset: 0,
});

describe('TagsComponent', () => {
  let tagSvc: jasmine.SpyObj<TagService>;

  beforeEach(async () => {
    tagSvc = jasmine.createSpyObj('TagService', ['list']);

    await TestBed.configureTestingModule({
      imports: [TagsComponent],
      providers: [
        { provide: TagService, useValue: tagSvc },
        provideRouter(routes),
      ],
    }).compileComponents();
  });

  it('shows skeleton while loading', () => {
    // list() never resolves during this test
    tagSvc.list.and.returnValue(new Subject<never>().asObservable());
    const fixture = TestBed.createComponent(TagsComponent);
    fixture.componentInstance.showSpinner = true;
    fixture.detectChanges();
    expect((fixture.nativeElement as HTMLElement).querySelector('.skeleton')).not.toBeNull();
  });

  it('renders tag list with name and count after load', () => {
    tagSvc.list.and.returnValue(of(TAGS_PAGE([
      { name: 'cat', image_count: 5 },
      { name: 'dog', image_count: 2 },
    ])));
    const fixture = TestBed.createComponent(TagsComponent);
    fixture.detectChanges();
    const items = (fixture.nativeElement as HTMLElement).querySelectorAll('.tag-item');
    expect(items.length).toBe(2);
    expect(items[0].textContent).toContain('cat');
    expect(items[0].textContent).toContain('5');
  });

  it('tags are ordered by count descending (service is called with count_desc)', () => {
    tagSvc.list.and.returnValue(of(TAGS_PAGE([])));
    const fixture = TestBed.createComponent(TagsComponent);
    fixture.detectChanges();
    expect(tagSvc.list).toHaveBeenCalledWith('', 500, 0, 'count_desc', 1);
  });

  it('shows empty state when tag list is empty', () => {
    tagSvc.list.and.returnValue(of(TAGS_PAGE([])));
    const fixture = TestBed.createComponent(TagsComponent);
    fixture.detectChanges();
    expect((fixture.nativeElement as HTMLElement).querySelector('.empty-state')).not.toBeNull();
  });

  it('shows error state on fetch failure', () => {
    tagSvc.list.and.returnValue(throwError(() => new Error('network')));
    const fixture = TestBed.createComponent(TagsComponent);
    fixture.detectChanges();
    expect((fixture.nativeElement as HTMLElement).querySelector('.error-card')).not.toBeNull();
  });

  it('retry button in error state calls load again', () => {
    tagSvc.list.and.returnValue(throwError(() => new Error('network')));
    const fixture = TestBed.createComponent(TagsComponent);
    fixture.detectChanges();
    spyOn(fixture.componentInstance, 'load');
    const btn = (fixture.nativeElement as HTMLElement).querySelector('.error-card .retry-btn') as HTMLButtonElement;
    expect(btn).not.toBeNull();
    btn.click();
    expect(fixture.componentInstance.load).toHaveBeenCalled();
  });

  it('each tag item links to /?tags=<tagname>', () => {
    tagSvc.list.and.returnValue(of(TAGS_PAGE([
      { name: 'funny', image_count: 3 },
    ])));
    const fixture = TestBed.createComponent(TagsComponent);
    fixture.detectChanges();
    const link = (fixture.nativeElement as HTMLElement).querySelector('.tag-item a') as HTMLAnchorElement;
    expect(link).not.toBeNull();
    expect(link.getAttribute('href')).toBe('/?tags=funny');
  });

  it('renders without requiring authentication', () => {
    tagSvc.list.and.returnValue(of(TAGS_PAGE([{ name: 'test', image_count: 1 }])));
    // No AuthService injected — component must not depend on it
    const fixture = TestBed.createComponent(TagsComponent);
    expect(() => fixture.detectChanges()).not.toThrow();
    expect((fixture.nativeElement as HTMLElement).querySelector('.tag-item')).not.toBeNull();
  });
});
