import { describe, it, expect, vi, beforeEach } from 'vitest';

vi.mock('../../auth.js', () => ({
  loadSession: vi.fn(),
  validateSession: vi.fn(),
  signIn: vi.fn(),
  saveSession: vi.fn(),
}));

vi.mock('./login.js', () => ({
  getGatewayUrl: vi.fn().mockReturnValue('http://localhost:14242'),
  promptLine: vi.fn(),
  promptSecret: vi.fn(),
}));

vi.mock('./piped-credentials.js', () => ({
  readCredentialsFromPipedStdin: vi.fn(),
}));

vi.mock('./daemon.js', () => ({
  readMeta: vi.fn(),
  writeMeta: vi.fn(),
}));

import { ensureSession } from './token-ops.js';
import { loadSession, validateSession, signIn, saveSession } from '../../auth.js';
import { promptLine, promptSecret } from './login.js';
import { readCredentialsFromPipedStdin } from './piped-credentials.js';

const URL = 'http://localhost:14242';

function asNonTTY(): void {
  Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true });
}

describe('ensureSession — #1394 credential precedence (flag > piped stdin > prompt)', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(loadSession).mockReturnValue(null);
    asNonTTY();
  });

  it('stored valid session wins; no credentials touched', async () => {
    vi.mocked(loadSession).mockReturnValue({ cookie: 'SESS', email: 'a@b.c' } as never);
    vi.mocked(validateSession).mockResolvedValue(true);
    await expect(ensureSession(URL)).resolves.toBe('SESS');
    expect(signIn).not.toHaveBeenCalled();
  });

  it('flag email + stdin password: FLAG wins for email, stdin supplies the password', async () => {
    vi.mocked(signIn).mockResolvedValue({ cookie: 'NEW', email: 'flag@b.c' } as never);
    vi.mocked(readCredentialsFromPipedStdin).mockResolvedValue({
      email: 'stdin@b.c',
      password: 'stdin-pw',
    });

    await ensureSession(URL, { email: 'flag@b.c' });

    expect(signIn).toHaveBeenCalledWith(URL, 'flag@b.c', 'stdin-pw');
    expect(promptLine).not.toHaveBeenCalled();
    expect(promptSecret).not.toHaveBeenCalled();
  });

  it('stdin-only path (no flag): both credentials from piped lines', async () => {
    vi.mocked(signIn).mockResolvedValue({ cookie: 'NEW2', email: 's@b.c' } as never);
    vi.mocked(readCredentialsFromPipedStdin).mockResolvedValue({
      email: 's@b.c',
      password: 'spw',
    });

    await ensureSession(URL);
    expect(signIn).toHaveBeenCalledWith(URL, 's@b.c', 'spw');
  });

  it('no credentials headless → exit(2) with --email guidance; signIn untouched', async () => {
    vi.mocked(readCredentialsFromPipedStdin).mockResolvedValue({ email: null, password: null });
    const exit = vi.spyOn(process, 'exit').mockImplementation((() => {
      throw new Error('EXIT');
    }) as never);
    const err = vi.spyOn(console, 'error').mockImplementation(() => {});

    await expect(ensureSession(URL)).rejects.toThrow('EXIT');
    expect(exit).toHaveBeenCalledWith(2);
    expect(err).toHaveBeenCalledWith(expect.stringContaining('--email'));
    expect(signIn).not.toHaveBeenCalled();

    exit.mockRestore();
    err.mockRestore();
  });

  it('successful sign-in persists the session', async () => {
    vi.mocked(signIn).mockResolvedValue({ cookie: 'C', email: 'a@b.c' } as never);
    vi.mocked(readCredentialsFromPipedStdin).mockResolvedValue({
      email: 'a@b.c',
      password: 'pw',
    });

    await ensureSession(URL);
    expect(saveSession).toHaveBeenCalledWith(URL, expect.anything());
  });
});
