import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

const { mockUseApi } = vi.hoisted(() => ({
  mockUseApi: vi.fn(),
}));

const {
  mockCreateHousehold,
  mockJoinHousehold,
  mockGetHousehold,
  mockUpdateHousehold,
  mockGenerateInviteCode,
} = vi.hoisted(() => ({
  mockCreateHousehold: vi.fn(),
  mockJoinHousehold: vi.fn(),
  mockGetHousehold: vi.fn(),
  mockUpdateHousehold: vi.fn(),
  mockGenerateInviteCode: vi.fn(),
}));

vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi }));
vi.mock('@/services/households', () => ({
  createHousehold: mockCreateHousehold,
  joinHousehold: mockJoinHousehold,
  getHousehold: mockGetHousehold,
  updateHousehold: mockUpdateHousehold,
  generateInviteCode: mockGenerateInviteCode,
}));

import SettingsPage from '../../../../src/app/(dashboard)/settings/page';

const household = {
  name: 'My House',
  inviteCode: 'ABC123',
  members: [{ userId: 'u-1', role: 'admin' }],
};

beforeEach(() => {
  vi.clearAllMocks();
});

describe('SettingsPage', () => {
  it('shows loading skeleton when loading', () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: true, refreshProfile: vi.fn() });
    render(<SettingsPage />);
    expect(screen.getByText('Settings')).toBeInTheDocument();
    expect(screen.queryByText('Household')).not.toBeInTheDocument();
  });

  it('shows create/join forms when no household', () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false, refreshProfile: vi.fn() });
    render(<SettingsPage />);
    expect(screen.getByPlaceholderText('Household name')).toBeInTheDocument();
    expect(screen.getByPlaceholderText('Invite code')).toBeInTheDocument();
  });

  it('shows account section always', () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false, refreshProfile: vi.fn() });
    render(<SettingsPage />);
    expect(screen.getByText('Account')).toBeInTheDocument();
    expect(screen.getByRole('link', { name: /Manage Keycloak Account/ })).toBeInTheDocument();
  });

  it('creates a household on form submit', async () => {
    const refreshProfile = vi.fn();
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false, refreshProfile });
    mockCreateHousehold.mockResolvedValue({});
    render(<SettingsPage />);

    await userEvent.type(screen.getByPlaceholderText('Household name'), 'New Home');
    await userEvent.click(screen.getByRole('button', { name: 'Create' }));

    await waitFor(() => expect(mockCreateHousehold).toHaveBeenCalledWith('New Home'));
    expect(refreshProfile).toHaveBeenCalled();
  });

  it('shows error when create fails', async () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false, refreshProfile: vi.fn() });
    mockCreateHousehold.mockRejectedValue(new Error('Already exists'));
    render(<SettingsPage />);

    await userEvent.type(screen.getByPlaceholderText('Household name'), 'New Home');
    await userEvent.click(screen.getByRole('button', { name: 'Create' }));

    await waitFor(() => expect(screen.getByText('Already exists')).toBeInTheDocument());
  });

  it('joins a household on form submit', async () => {
    const refreshProfile = vi.fn();
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false, refreshProfile });
    mockJoinHousehold.mockResolvedValue({});
    render(<SettingsPage />);

    await userEvent.type(screen.getByPlaceholderText('Invite code'), 'XYZ999');
    await userEvent.click(screen.getByRole('button', { name: 'Join' }));

    await waitFor(() => expect(mockJoinHousehold).toHaveBeenCalledWith('XYZ999'));
    expect(refreshProfile).toHaveBeenCalled();
  });

  it('shows error when join fails', async () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false, refreshProfile: vi.fn() });
    mockJoinHousehold.mockRejectedValue(new Error('Invalid code'));
    render(<SettingsPage />);

    await userEvent.type(screen.getByPlaceholderText('Invite code'), 'BAD');
    await userEvent.click(screen.getByRole('button', { name: 'Join' }));

    await waitFor(() => expect(screen.getByText('Invalid code')).toBeInTheDocument());
  });

  it('loads and displays household details when householdId exists', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    render(<SettingsPage />);

    await waitFor(() => expect(screen.getByText('My House')).toBeInTheDocument());
    expect(screen.getByText('ABC123')).toBeInTheDocument();
    expect(screen.getByText('1')).toBeInTheDocument(); // member count
  });

  it('shows loading household details text initially', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockReturnValue(new Promise(() => {})); // never resolves
    render(<SettingsPage />);
    expect(screen.getByText('Loading household details...')).toBeInTheDocument();
  });

  it('allows editing the household name', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    mockUpdateHousehold.mockResolvedValue({ ...household, name: 'Updated Home' });
    render(<SettingsPage />);

    await waitFor(() => screen.getByText('My House'));
    await userEvent.click(screen.getByRole('button', { name: 'Edit' }));

    const input = screen.getByDisplayValue('My House');
    await userEvent.clear(input);
    await userEvent.type(input, 'Updated Home');
    await userEvent.click(screen.getByRole('button', { name: 'Save' }));

    await waitFor(() =>
      expect(mockUpdateHousehold).toHaveBeenCalledWith('hh1', { name: 'Updated Home' }),
    );
  });

  it('shows error when name update fails', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    mockUpdateHousehold.mockRejectedValue(new Error('Name taken'));
    render(<SettingsPage />);

    await waitFor(() => screen.getByText('My House'));
    await userEvent.click(screen.getByRole('button', { name: 'Edit' }));
    const input = screen.getByDisplayValue('My House');
    await userEvent.clear(input);
    await userEvent.type(input, 'Other Home');
    await userEvent.click(screen.getByRole('button', { name: 'Save' }));

    await waitFor(() => expect(screen.getByText('Name taken')).toBeInTheDocument());
  });

  it('cancels editing name', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    render(<SettingsPage />);

    await waitFor(() => screen.getByText('My House'));
    await userEvent.click(screen.getByRole('button', { name: 'Edit' }));
    await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));

    expect(screen.getByText('My House')).toBeInTheDocument();
    expect(screen.queryByDisplayValue('My House')).not.toBeInTheDocument();
  });

  it('regenerates invite code', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    mockGenerateInviteCode.mockResolvedValue({ ...household, inviteCode: 'NEW999' });
    render(<SettingsPage />);

    await waitFor(() => screen.getByText('ABC123'));
    await userEvent.click(screen.getByRole('button', { name: 'Regenerate' }));

    await waitFor(() => expect(screen.getByText('NEW999')).toBeInTheDocument());
  });

  it('shows error when regenerate fails', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    mockGenerateInviteCode.mockRejectedValue(new Error('Server error'));
    render(<SettingsPage />);

    await waitFor(() => screen.getByText('ABC123'));
    await userEvent.click(screen.getByRole('button', { name: 'Regenerate' }));

    await waitFor(() => expect(screen.getByText('Server error')).toBeInTheDocument());
  });

  it('saves unchanged name without calling API', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    render(<SettingsPage />);

    await waitFor(() => screen.getByText('My House'));
    await userEvent.click(screen.getByRole('button', { name: 'Edit' }));
    await userEvent.click(screen.getByRole('button', { name: 'Save' }));

    expect(mockUpdateHousehold).not.toHaveBeenCalled();
  });

  it('shows fallback error when non-Error thrown on create household', async () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false, refreshProfile: vi.fn() });
    mockCreateHousehold.mockRejectedValue('create failed');
    render(<SettingsPage />);

    await userEvent.type(screen.getByPlaceholderText('Household name'), 'My Home');
    await userEvent.click(screen.getByRole('button', { name: 'Create' }));

    await waitFor(() => expect(screen.getByText('Failed to create household')).toBeInTheDocument());
  });

  it('shows fallback error when non-Error thrown on join household', async () => {
    mockUseApi.mockReturnValue({ householdId: null, isLoading: false, refreshProfile: vi.fn() });
    mockJoinHousehold.mockRejectedValue('join failed');
    render(<SettingsPage />);

    await userEvent.type(screen.getByPlaceholderText('Invite code'), 'XYZ123');
    await userEvent.click(screen.getByRole('button', { name: 'Join' }));

    await waitFor(() => expect(screen.getByText('Failed to join household')).toBeInTheDocument());
  });

  it('shows fallback error when non-Error thrown on name update', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    mockUpdateHousehold.mockRejectedValue('update failed');
    render(<SettingsPage />);

    await waitFor(() => screen.getByText('My House'));
    await userEvent.click(screen.getByRole('button', { name: 'Edit' }));
    const input = screen.getByDisplayValue('My House');
    await userEvent.clear(input);
    await userEvent.type(input, 'New Name');
    await userEvent.click(screen.getByRole('button', { name: 'Save' }));

    await waitFor(() => expect(screen.getByText('Failed to update name')).toBeInTheDocument());
  });

  it('shows fallback error when non-Error thrown on regenerate', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    mockGenerateInviteCode.mockRejectedValue('regen failed');
    render(<SettingsPage />);

    await waitFor(() => screen.getByText('My House'));
    await userEvent.click(screen.getByRole('button', { name: 'Regenerate' }));

    await waitFor(() =>
      expect(screen.getByText('Failed to regenerate invite code')).toBeInTheDocument(),
    );
  });

  it('shows validation error when saving empty name', async () => {
    mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, refreshProfile: vi.fn() });
    mockGetHousehold.mockResolvedValue(household);
    render(<SettingsPage />);

    await waitFor(() => screen.getByText('My House'));
    await userEvent.click(screen.getByRole('button', { name: 'Edit' }));
    const input = screen.getByDisplayValue('My House');
    await userEvent.clear(input);
    await userEvent.click(screen.getByRole('button', { name: 'Save' }));

    expect(screen.getByText('Name cannot be empty')).toBeInTheDocument();
  });
});
