import { signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, convertToParamMap, provideRouter } from '@angular/router';
import { Subject } from 'rxjs';
import { PlayerDetail } from './player-detail';
import { AuthStore } from '../../../core/auth/auth-store';
import { TeamStore } from '../../../core/team/team-store';
import { environment } from '../../../../environments/environment';

describe('PlayerDetail', () => {
  const teamsApiUrl = `${environment.apiUrl}teams/5`;
  let httpMock: HttpTestingController;
  let refreshTeam: ReturnType<typeof vi.fn>;
  let currentUser: ReturnType<typeof signal>;
  let closeDialog: Subject<boolean>;
  let dialog: { open: ReturnType<typeof vi.fn> };

  function makeTeam(playerOverrides: Partial<Record<string, unknown>> = {}) {
    return {
      id: 5,
      name: 'Team A',
      alias: 'team-a',
      balance: 0,
      players: [
        {
          id: 7,
          firstName: 'Alex',
          lastName: 'Muster',
          balance: -12,
          active: true,
          teamRole: { id: 1, name: 'player' },
          ...playerOverrides,
        },
      ],
    };
  }

  async function create(
    team: ReturnType<typeof makeTeam>,
    user: unknown,
    transactions: unknown[] = [],
  ) {
    refreshTeam = vi.fn();
    currentUser = signal(user);
    closeDialog = new Subject<boolean>();
    dialog = { open: vi.fn(() => ({ afterClosed: () => closeDialog.asObservable() })) };

    await TestBed.configureTestingModule({
      imports: [PlayerDetail],
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        provideRouter([]),
        {
          provide: TeamStore,
          useValue: { team: signal(team), loading: signal(false), refreshTeam },
        },
        { provide: AuthStore, useValue: { currentUser } },
        { provide: MatDialog, useValue: dialog },
        {
          provide: ActivatedRoute,
          useValue: { snapshot: { paramMap: convertToParamMap({ playerId: '7' }) } },
        },
      ],
    }).compileComponents();
    const fixture = TestBed.createComponent(PlayerDetail);
    fixture.detectChanges();
    httpMock = TestBed.inject(HttpTestingController);
    httpMock.expectOne(`${teamsApiUrl}/players/7/transactions`).flush(transactions);
    await fixture.whenStable();
    fixture.detectChanges();
    return fixture;
  }

  afterEach(() => httpMock.verify());

  it('renders the selected player and transaction history', async () => {
    const fixture = await create(makeTeam({ balance: -12 }), { id: 99, role: { id: 2 } });
    expect(fixture.nativeElement.textContent).toContain('Alex Muster');
    expect(fixture.nativeElement.textContent).toContain('-12,00');
  });

  it('renders player inflow, reversal outflow, and neutral amounts with their cash-flow meaning', async () => {
    const fixture = await create(makeTeam(), { id: 99, role: { id: 2 } }, [
      {
        id: 1,
        date: '2026-07-31',
        amount: 12,
        type: { id: 0, name: 'payment' },
      },
      {
        id: 2,
        date: '2026-07-30',
        amount: -3,
        type: { id: 0, name: 'payment' },
      },
      {
        id: 3,
        date: '2026-07-29',
        amount: 5,
        type: { id: 11, name: 'fine' },
      },
    ]);
    const amounts = [
      ...fixture.nativeElement.querySelectorAll(
        'app-transaction-amount [data-testid="transaction-amount"]',
      ),
    ] as HTMLElement[];

    expect(amounts).toHaveLength(3);
    expect(amounts[0].classList).toContain('inflow');
    expect(amounts[1].classList).toContain('outflow');
    expect(amounts[2].classList).toContain('neutral');
    expect(
      amounts.map((amount) => amount.querySelector('.transaction-amount__sign')?.textContent),
    ).toEqual(['+', '−', '']);
  });

  it('hides the manage controls for a user without team-manager rights', async () => {
    const fixture = await create(makeTeam(), { id: 99, role: { id: 2 } });
    const button = [...fixture.nativeElement.querySelectorAll('button')].find(
      (b: HTMLButtonElement) => b.textContent?.includes('Deaktivieren'),
    );
    expect(button).toBeUndefined();
  });

  it('shows the manage controls for a global admin', async () => {
    const fixture = await create(makeTeam(), { id: 1, role: { id: 1 } });
    const button = [...fixture.nativeElement.querySelectorAll('button')].find(
      (b: HTMLButtonElement) => b.textContent?.includes('Deaktivieren'),
    );
    expect(button).toBeDefined();
  });

  it('shows the manage controls for a captain-and-above team member', async () => {
    const fixture = await create(
      makeTeam({ user: { id: 42 }, teamRole: { id: 3, name: 'captain' } }),
      { id: 42, role: { id: 2 } },
    );
    const button = [...fixture.nativeElement.querySelectorAll('button')].find(
      (b: HTMLButtonElement) => b.textContent?.includes('Deaktivieren'),
    );
    expect(button).toBeDefined();
  });

  it('deactivates the player on confirm and refreshes the team', async () => {
    const fixture = await create(makeTeam(), { id: 1, role: { id: 1 } });
    const button = [...fixture.nativeElement.querySelectorAll('button')].find(
      (b: HTMLButtonElement) => b.textContent?.includes('Deaktivieren'),
    ) as HTMLButtonElement;
    button.click();

    expect(dialog.open).toHaveBeenCalled();
    expect(dialog.open.mock.calls[0][1].data.message).toContain('Alex Muster');
    httpMock.expectNone(`${teamsApiUrl}/players/7/active`);

    closeDialog.next(true);
    const request = httpMock.expectOne(`${teamsApiUrl}/players/7/active`);
    expect(request.request.method).toBe('PATCH');
    expect(request.request.body).toEqual({ active: false });
    request.flush({ id: 7, active: false });

    expect(refreshTeam).toHaveBeenCalled();
  });

  it('does not call the API when the confirmation dialog is dismissed', async () => {
    const fixture = await create(makeTeam(), { id: 1, role: { id: 1 } });
    const button = [...fixture.nativeElement.querySelectorAll('button')].find(
      (b: HTMLButtonElement) => b.textContent?.includes('Deaktivieren'),
    ) as HTMLButtonElement;
    button.click();
    closeDialog.next(false);
    httpMock.expectNone(`${teamsApiUrl}/players/7/active`);
    expect(refreshTeam).not.toHaveBeenCalled();
  });

  it('changes the team-role and refreshes the team', async () => {
    const fixture = await create(makeTeam(), { id: 1, role: { id: 1 } });
    fixture.componentInstance['changeTeamRole'](4);

    const request = httpMock.expectOne(`${teamsApiUrl}/players/7/team-role`);
    expect(request.request.method).toBe('PATCH');
    expect(request.request.body).toEqual({ teamRoleId: 4 });
    request.flush({ id: 7, teamRole: { id: 4 } });

    expect(refreshTeam).toHaveBeenCalled();
  });
});
