import { TestBed } from '@angular/core/testing';
import { Component, signal, computed } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { usePageTitle } from './use-page-title.function';
import { TabService } from '@isa/core/tabs';
import { TITLE_PREFIX } from './title-prefix';

describe('usePageTitle', () => {
  let titleServiceMock: { setTitle: ReturnType<typeof vi.fn>; getTitle: ReturnType<typeof vi.fn> };
  let tabServiceMock: {
    activatedTabId: ReturnType<typeof signal<number | null>>;
    patchTab: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    // Arrange - Create mocks
    titleServiceMock = {
      setTitle: vi.fn(),
      getTitle: vi.fn().mockReturnValue(''),
    };

    tabServiceMock = {
      activatedTabId: signal<number | null>(456),
      patchTab: vi.fn(),
    };

    TestBed.configureTestingModule({
      providers: [
        { provide: Title, useValue: titleServiceMock },
        { provide: TITLE_PREFIX, useValue: 'ISA' },
        { provide: TabService, useValue: tabServiceMock },
      ],
    });
  });

  it('should set initial title on component creation', () => {
    // Arrange
    @Component({
      standalone: true,
      template: '<div>Test</div>',
    })
    class TestComponent {
      pageTitle = signal({ title: 'Dashboard' });

      constructor() {
        usePageTitle(this.pageTitle);
      }
    }

    // Act
    TestBed.createComponent(TestComponent);
    TestBed.flushEffects();

    // Assert
    expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Dashboard');
    expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
      name: 'Dashboard',
    });
  });

  it('should update title when signal changes', () => {
    // Arrange
    @Component({
      standalone: true,
      template: '<div>Test</div>',
    })
    class TestComponent {
      searchTerm = signal('');
      pageTitle = computed(() => {
        const term = this.searchTerm();
        return {
          title: term ? `Artikelsuche - "${term}"` : 'Artikelsuche',
        };
      });

      constructor() {
        usePageTitle(this.pageTitle);
      }
    }

    const fixture = TestBed.createComponent(TestComponent);
    const component = fixture.componentInstance;
    TestBed.flushEffects();

    // Clear previous calls
    vi.clearAllMocks();

    // Act
    component.searchTerm.set('Laptop');
    TestBed.flushEffects();

    // Assert
    expect(titleServiceMock.setTitle).toHaveBeenCalledWith(
      'ISA - Artikelsuche - "Laptop"'
    );
    expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
      name: 'Artikelsuche - "Laptop"',
    });
  });

  it('should not update tab when activatedTabId is null', () => {
    // Arrange
    tabServiceMock.activatedTabId.set(null);

    @Component({
      standalone: true,
      template: '<div>Test</div>',
    })
    class TestComponent {
      pageTitle = signal({ title: 'Profile' });

      constructor() {
        usePageTitle(this.pageTitle);
      }
    }

    // Act
    TestBed.createComponent(TestComponent);
    TestBed.flushEffects();

    // Assert
    expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Profile');
    expect(tabServiceMock.patchTab).not.toHaveBeenCalled();
  });

  it('should use custom prefix from TITLE_PREFIX', () => {
    // Arrange
    TestBed.overrideProvider(TITLE_PREFIX, { useValue: 'MyApp' });

    @Component({
      standalone: true,
      template: '<div>Test</div>',
    })
    class TestComponent {
      pageTitle = signal({ title: 'About' });

      constructor() {
        usePageTitle(this.pageTitle);
      }
    }

    // Act
    TestBed.createComponent(TestComponent);
    TestBed.flushEffects();

    // Assert
    expect(titleServiceMock.setTitle).toHaveBeenCalledWith('MyApp - About');
  });

  it('should handle multiple signal updates correctly', () => {
    // Arrange
    @Component({
      standalone: true,
      template: '<div>Test</div>',
    })
    class TestComponent {
      counter = signal(0);
      pageTitle = computed(() => ({ title: `Page ${this.counter()}` }));

      constructor() {
        usePageTitle(this.pageTitle);
      }
    }

    const fixture = TestBed.createComponent(TestComponent);
    const component = fixture.componentInstance;
    TestBed.flushEffects();

    // Clear initial call
    vi.clearAllMocks();

    // Act
    component.counter.set(1);
    TestBed.flushEffects();
    component.counter.set(2);
    TestBed.flushEffects();
    component.counter.set(3);
    TestBed.flushEffects();

    // Assert
    expect(titleServiceMock.setTitle).toHaveBeenCalledTimes(3);
    expect(titleServiceMock.setTitle).toHaveBeenNthCalledWith(1, 'ISA - Page 1');
    expect(titleServiceMock.setTitle).toHaveBeenNthCalledWith(2, 'ISA - Page 2');
    expect(titleServiceMock.setTitle).toHaveBeenNthCalledWith(3, 'ISA - Page 3');
  });

  describe('nested components', () => {
    it('should prioritize child component title over parent', () => {
      // Arrange - Parent component
      @Component({
        standalone: true,
        template: '<div>Parent</div>',
      })
      class ParentComponent {
        pageTitle = signal({ title: 'Dashboard' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Child component
      @Component({
        standalone: true,
        template: '<div>Child</div>',
      })
      class ChildComponent {
        pageTitle = signal({ title: 'Settings' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act - Create parent first
      TestBed.createComponent(ParentComponent);
      TestBed.flushEffects();

      expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Dashboard');

      vi.clearAllMocks();

      // Act - Create child (should win)
      TestBed.createComponent(ChildComponent);
      TestBed.flushEffects();

      // Assert - Child title should be set
      expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Settings');
    });

    it('should restore parent title when child component is destroyed', () => {
      // Arrange - Parent component
      @Component({
        standalone: true,
        template: '<div>Parent</div>',
      })
      class ParentComponent {
        pageTitle = signal({ title: 'Dashboard' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Child component
      @Component({
        standalone: true,
        template: '<div>Child</div>',
      })
      class ChildComponent {
        pageTitle = signal({ title: 'Settings' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act - Create both components
      TestBed.createComponent(ParentComponent);
      TestBed.flushEffects();

      const childFixture = TestBed.createComponent(ChildComponent);
      TestBed.flushEffects();

      vi.clearAllMocks();

      // Act - Destroy child
      childFixture.destroy();

      // Assert - Parent title should be restored
      expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Dashboard');
    });

    it('should handle parent title updates when child is active', () => {
      // Arrange - Parent component with mutable signal
      @Component({
        standalone: true,
        template: '<div>Parent</div>',
      })
      class ParentComponent {
        pageTitle = signal({ title: 'Dashboard' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Child component
      @Component({
        standalone: true,
        template: '<div>Child</div>',
      })
      class ChildComponent {
        pageTitle = signal({ title: 'Settings' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act - Create both
      const parentFixture = TestBed.createComponent(ParentComponent);
      const parent = parentFixture.componentInstance;
      TestBed.flushEffects();

      TestBed.createComponent(ChildComponent);
      TestBed.flushEffects();

      vi.clearAllMocks();

      // Act - Parent tries to update (should be ignored while child is active)
      parent.pageTitle.set({ title: 'Dashboard Updated' });
      TestBed.flushEffects();

      // Assert - Title should still be child's (parent update ignored)
      expect(titleServiceMock.setTitle).not.toHaveBeenCalled();
    });

    it('should allow parent title updates after child is destroyed', () => {
      // Arrange - Parent component with mutable signal
      @Component({
        standalone: true,
        template: '<div>Parent</div>',
      })
      class ParentComponent {
        pageTitle = signal({ title: 'Dashboard' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Child component
      @Component({
        standalone: true,
        template: '<div>Child</div>',
      })
      class ChildComponent {
        pageTitle = signal({ title: 'Settings' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act - Create both
      const parentFixture = TestBed.createComponent(ParentComponent);
      const parent = parentFixture.componentInstance;
      TestBed.flushEffects();

      const childFixture = TestBed.createComponent(ChildComponent);
      TestBed.flushEffects();

      // Destroy child
      childFixture.destroy();

      vi.clearAllMocks();

      // Act - Parent updates now (should work)
      parent.pageTitle.set({ title: 'Dashboard Updated' });
      TestBed.flushEffects();

      // Assert - Parent update should be reflected
      expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Dashboard Updated');
    });

    it('should handle three-level nesting (grandparent → parent → child)', () => {
      // Arrange - Three levels of components
      @Component({
        standalone: true,
        template: '<div>Grandparent</div>',
      })
      class GrandparentComponent {
        pageTitle = signal({ title: 'Main' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      @Component({
        standalone: true,
        template: '<div>Parent</div>',
      })
      class ParentComponent {
        pageTitle = signal({ title: 'Dashboard' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      @Component({
        standalone: true,
        template: '<div>Child</div>',
      })
      class ChildComponent {
        pageTitle = signal({ title: 'Settings' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act - Create all three
      TestBed.createComponent(GrandparentComponent);
      TestBed.flushEffects();

      TestBed.createComponent(ParentComponent);
      TestBed.flushEffects();

      const childFixture = TestBed.createComponent(ChildComponent);
      TestBed.flushEffects();

      // Verify child is active
      expect(titleServiceMock.setTitle).toHaveBeenLastCalledWith('ISA - Settings');

      vi.clearAllMocks();

      // Act - Destroy child
      childFixture.destroy();

      // Assert - Parent title should be restored
      expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Dashboard');
    });
  });

  describe('subtitle', () => {
    it('should set tab subtitle when provided', () => {
      // Arrange
      @Component({
        standalone: true,
        template: '<div>Test</div>',
      })
      class TestComponent {
        pageTitle = signal({ title: 'Dashboard', subtitle: 'Overview' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act
      TestBed.createComponent(TestComponent);
      TestBed.flushEffects();

      // Assert
      expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
        name: 'Dashboard',
        subtitle: 'Overview',
      });
    });

    it('should not include subtitle in patchTab when not provided', () => {
      // Arrange
      @Component({
        standalone: true,
        template: '<div>Test</div>',
      })
      class TestComponent {
        pageTitle = signal({ title: 'Dashboard' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act
      TestBed.createComponent(TestComponent);
      TestBed.flushEffects();

      // Assert
      const callArgs = tabServiceMock.patchTab.mock.calls[0];
      expect(callArgs[0]).toBe(456);
      expect(callArgs[1]).toEqual({ name: 'Dashboard' });
      expect(callArgs[1]).not.toHaveProperty('subtitle');
    });

    it('should maintain subtitle across title signal changes', () => {
      // Arrange
      @Component({
        standalone: true,
        template: '<div>Test</div>',
      })
      class TestComponent {
        searchTerm = signal('');
        pageTitle = computed(() => {
          const term = this.searchTerm();
          return {
            title: term ? `Search - "${term}"` : 'Search',
            subtitle: 'Active',
          };
        });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      const fixture = TestBed.createComponent(TestComponent);
      const component = fixture.componentInstance;
      TestBed.flushEffects();

      vi.clearAllMocks();

      // Act
      component.searchTerm.set('Laptop');
      TestBed.flushEffects();

      // Assert
      expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
        name: 'Search - "Laptop"',
        subtitle: 'Active',
      });
    });

    it('should handle nested components each with different subtitles', () => {
      // Arrange - Parent component
      @Component({
        standalone: true,
        template: '<div>Parent</div>',
      })
      class ParentComponent {
        pageTitle = signal({ title: 'Dashboard', subtitle: 'Main' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Child component
      @Component({
        standalone: true,
        template: '<div>Child</div>',
      })
      class ChildComponent {
        pageTitle = signal({ title: 'Settings', subtitle: 'Preferences' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act - Create parent first
      TestBed.createComponent(ParentComponent);
      TestBed.flushEffects();

      expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
        name: 'Dashboard',
        subtitle: 'Main',
      });

      vi.clearAllMocks();

      // Act - Create child (should win)
      TestBed.createComponent(ChildComponent);
      TestBed.flushEffects();

      // Assert - Child subtitle should be set
      expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
        name: 'Settings',
        subtitle: 'Preferences',
      });
    });
  });

  describe('optional title/subtitle', () => {
    it('should skip document title update when title is undefined', () => {
      // Arrange
      @Component({
        standalone: true,
        template: '<div>Test</div>',
      })
      class TestComponent {
        pageTitle = signal({ subtitle: 'Loading' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act
      TestBed.createComponent(TestComponent);
      TestBed.flushEffects();

      // Assert
      expect(titleServiceMock.setTitle).not.toHaveBeenCalled();
      expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
        subtitle: 'Loading',
      });
    });

    it('should skip tab subtitle update when subtitle is undefined', () => {
      // Arrange
      @Component({
        standalone: true,
        template: '<div>Test</div>',
      })
      class TestComponent {
        pageTitle = signal({ title: 'Dashboard' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act
      TestBed.createComponent(TestComponent);
      TestBed.flushEffects();

      // Assert
      expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Dashboard');
      const callArgs = tabServiceMock.patchTab.mock.calls[0];
      expect(callArgs[1]).toEqual({ name: 'Dashboard' });
      expect(callArgs[1]).not.toHaveProperty('subtitle');
    });

    it('should handle empty object (skip all updates)', () => {
      // Arrange
      @Component({
        standalone: true,
        template: '<div>Test</div>',
      })
      class TestComponent {
        pageTitle = signal({});

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act
      TestBed.createComponent(TestComponent);
      TestBed.flushEffects();

      // Assert
      expect(titleServiceMock.setTitle).not.toHaveBeenCalled();
      expect(tabServiceMock.patchTab).not.toHaveBeenCalled();
    });

    it('should handle dynamic changes from defined to undefined', () => {
      // Arrange
      @Component({
        standalone: true,
        template: '<div>Test</div>',
      })
      class TestComponent {
        showTitle = signal(true);
        pageTitle = computed(() => ({
          title: this.showTitle() ? 'Dashboard' : undefined,
          subtitle: 'Active',
        }));

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      const fixture = TestBed.createComponent(TestComponent);
      const component = fixture.componentInstance;
      TestBed.flushEffects();

      // Verify initial state
      expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Dashboard');

      vi.clearAllMocks();

      // Act - Hide title
      component.showTitle.set(false);
      TestBed.flushEffects();

      // Assert - Title should not be updated, but subtitle should
      expect(titleServiceMock.setTitle).not.toHaveBeenCalled();
      expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
        subtitle: 'Active',
      });
    });

    it('should handle both title and subtitle', () => {
      // Arrange
      @Component({
        standalone: true,
        template: '<div>Test</div>',
      })
      class TestComponent {
        pageTitle = signal({ title: 'Orders', subtitle: '3 items' });

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      // Act
      TestBed.createComponent(TestComponent);
      TestBed.flushEffects();

      // Assert
      expect(titleServiceMock.setTitle).toHaveBeenCalledWith('ISA - Orders');
      expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
        name: 'Orders',
        subtitle: '3 items',
      });
    });

    it('should handle subtitle only (no title)', () => {
      // Arrange
      @Component({
        standalone: true,
        template: '<div>Test</div>',
      })
      class TestComponent {
        count = signal(5);
        pageTitle = computed(() => ({
          subtitle: `${this.count()} items`,
        }));

        constructor() {
          usePageTitle(this.pageTitle);
        }
      }

      const fixture = TestBed.createComponent(TestComponent);
      const component = fixture.componentInstance;
      TestBed.flushEffects();

      // Assert initial
      expect(titleServiceMock.setTitle).not.toHaveBeenCalled();
      expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
        subtitle: '5 items',
      });

      vi.clearAllMocks();

      // Act - Update count
      component.count.set(10);
      TestBed.flushEffects();

      // Assert - Only subtitle updates
      expect(titleServiceMock.setTitle).not.toHaveBeenCalled();
      expect(tabServiceMock.patchTab).toHaveBeenCalledWith(456, {
        subtitle: '10 items',
      });
    });
  });
});
