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

import { clearStoredAuthSession } from '@/lib/authStorage';
import { clearSupabaseAuthStorage, getSupabaseClient } from '@/lib/supabase';
import {
  changePasswordBeforeLogin,
  logout,
  lookupRecoveryUsernameByEmail,
  register,
  resetPasswordByEmail,
  restoreSession,
} from '@/services/authService';

vi.mock('@/lib/supabase', () => ({
  getSupabaseClient: vi.fn(),
  clearSupabaseAuthStorage: vi.fn(),
}));

vi.mock('@/lib/authStorage', () => ({
  clearStoredAuthSession: vi.fn(),
}));

describe('authService', () => {
  beforeEach(() => {
    vi.resetAllMocks();
  });

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

  it('clears persisted auth state when the refresh token is invalid during restore', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue({
      auth: {
        getSession: vi.fn().mockRejectedValue(new Error('Invalid Refresh Token: Refresh Token Not Found')),
      },
    } as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(restoreSession()).resolves.toBeNull();

    expect(clearStoredAuthSession).toHaveBeenCalledTimes(1);
    expect(clearSupabaseAuthStorage).toHaveBeenCalledTimes(1);
  });

  it('rethrows unrelated restore errors', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue({
      auth: {
        getSession: vi.fn().mockRejectedValue(new Error('network down')),
      },
    } as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(restoreSession()).rejects.toThrow('network down');

    expect(clearStoredAuthSession).not.toHaveBeenCalled();
    expect(clearSupabaseAuthStorage).not.toHaveBeenCalled();
  });

  it('clears persisted auth state even when logout fails', async () => {
    vi.mocked(getSupabaseClient).mockReturnValue({
      auth: {
        signOut: vi.fn().mockRejectedValue(new Error('sign out failed')),
      },
    } as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(logout()).rejects.toThrow('sign out failed');

    expect(clearStoredAuthSession).toHaveBeenCalledTimes(1);
    expect(clearSupabaseAuthStorage).toHaveBeenCalledTimes(1);
  });

  it('stores the recovery email when registering a user', async () => {
    const signUp = vi.fn().mockResolvedValue({
      data: {
        user: { id: 'user-1', email: 'mia@autoprocurement.local' },
        session: { access_token: 'token-1' },
      },
      error: null,
    });
    const profileSingle = vi.fn().mockResolvedValue({
      data: { username: 'mia', role: 'user' },
      error: null,
    });
    const profileEq = vi.fn().mockReturnValue({ single: profileSingle });
    const select = vi.fn().mockReturnValue({ eq: profileEq });
    const from = vi.fn().mockReturnValue({ select });
    const rpc = vi.fn().mockResolvedValue({ error: null });

    vi.mocked(getSupabaseClient).mockReturnValue({
      auth: { signUp },
      from,
      rpc,
    } as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(register({
      username: 'Mia',
      email: ' Mia@Example.COM ',
      password: 'password-1',
    })).resolves.toMatchObject({
      token: 'token-1',
      user: { username: 'mia', role: 'user' },
    });

    expect(signUp).toHaveBeenCalledWith({
      email: 'mia@autoprocurement.local',
      password: 'password-1',
      options: {
        data: { username: 'Mia', recovery_email: 'mia@example.com' },
      },
    });
    expect(rpc).toHaveBeenCalledWith('set_own_recovery_email', {
      recovery_email_input: 'mia@example.com',
    });
  });

  it('changes password before login after verifying the current password', async () => {
    const signInWithPassword = vi.fn().mockResolvedValue({ error: null });
    const updateUser = vi.fn().mockResolvedValue({ error: null });
    const signOut = vi.fn().mockResolvedValue({ error: null });

    vi.mocked(getSupabaseClient).mockReturnValue({
      auth: {
        signInWithPassword,
        updateUser,
        signOut,
      },
    } as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(changePasswordBeforeLogin({
      username: 'Mia',
      currentPassword: 'old-password',
      newPassword: 'new-password',
    })).resolves.toBeUndefined();

    expect(signInWithPassword).toHaveBeenCalledWith({
      email: 'mia@autoprocurement.local',
      password: 'old-password',
    });
    expect(updateUser).toHaveBeenCalledWith({ password: 'new-password' });
    expect(signOut).toHaveBeenCalledTimes(1);
    expect(clearStoredAuthSession).toHaveBeenCalledTimes(1);
    expect(clearSupabaseAuthStorage).toHaveBeenCalledTimes(1);
  });

  it('rejects password changes before login when the current password is wrong', async () => {
    const signInWithPassword = vi.fn().mockResolvedValue({ error: new Error('invalid credentials') });
    const updateUser = vi.fn();
    const signOut = vi.fn();

    vi.mocked(getSupabaseClient).mockReturnValue({
      auth: {
        signInWithPassword,
        updateUser,
        signOut,
      },
    } as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(changePasswordBeforeLogin({
      username: 'mia',
      currentPassword: 'wrong-password',
      newPassword: 'new-password',
    })).rejects.toThrow('原密码不正确');

    expect(updateUser).not.toHaveBeenCalled();
    expect(signOut).not.toHaveBeenCalled();
    expect(clearStoredAuthSession).not.toHaveBeenCalled();
    expect(clearSupabaseAuthStorage).not.toHaveBeenCalled();
  });

  it('clears the temporary auth state when updating the password fails before login', async () => {
    const signInWithPassword = vi.fn().mockResolvedValue({ error: null });
    const updateUser = vi.fn().mockResolvedValue({ error: new Error('weak password') });
    const signOut = vi.fn().mockResolvedValue({ error: null });

    vi.mocked(getSupabaseClient).mockReturnValue({
      auth: {
        signInWithPassword,
        updateUser,
        signOut,
      },
    } as unknown as ReturnType<typeof getSupabaseClient>);

    await expect(changePasswordBeforeLogin({
      username: 'mia',
      currentPassword: 'old-password',
      newPassword: '123',
    })).rejects.toThrow('密码修改失败，请重试');

    expect(signOut).toHaveBeenCalledTimes(1);
    expect(clearStoredAuthSession).toHaveBeenCalledTimes(1);
    expect(clearSupabaseAuthStorage).toHaveBeenCalledTimes(1);
  });

  it('resets password by recovery email through the automation api', async () => {
    const fetchMock = vi.fn<typeof fetch>().mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ success: true }),
    } as Response);
    vi.stubGlobal('fetch', fetchMock);

    await expect(resetPasswordByEmail({
      email: ' Mia@Example.COM ',
      newPassword: 'new-password',
    })).resolves.toBeUndefined();

    expect(fetchMock).toHaveBeenCalledWith('/api/auth/password-reset-by-email', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: 'mia@example.com',
        newPassword: 'new-password',
      }),
    });
  });

  it('surfaces password reset by email api errors', async () => {
    const fetchMock = vi.fn<typeof fetch>().mockResolvedValue({
      ok: false,
      status: 404,
      json: async () => ({ error: '邮箱不存在或暂不可重置' }),
    } as Response);
    vi.stubGlobal('fetch', fetchMock);

    await expect(resetPasswordByEmail({
      email: 'mia@example.com',
      newPassword: 'new-password',
    })).rejects.toThrow('邮箱不存在或暂不可重置');
  });

  it('looks up the recovery username by email through the automation api', async () => {
    const fetchMock = vi.fn<typeof fetch>().mockResolvedValue({
      ok: true,
      status: 200,
      json: async () => ({ username: 'mia77' }),
    } as Response);
    vi.stubGlobal('fetch', fetchMock);

    await expect(lookupRecoveryUsernameByEmail({
      email: ' Mia@Example.COM ',
    })).resolves.toBe('mia77');

    expect(fetchMock).toHaveBeenCalledWith('/api/auth/recovery-username-by-email', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: 'mia@example.com',
      }),
    });
  });

  it('returns null when recovery email has no matching username', async () => {
    const fetchMock = vi.fn<typeof fetch>().mockResolvedValue({
      ok: false,
      status: 404,
      json: async () => ({ error: '邮箱未匹配到注册用户名' }),
    } as Response);
    vi.stubGlobal('fetch', fetchMock);

    await expect(lookupRecoveryUsernameByEmail({
      email: 'unknown@example.com',
    })).resolves.toBeNull();
  });
});
