import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { GtasksConfig } from '../../config/schema.js';

// Hoisted mocks so vi.mock factories can reference them
const { mockTasklistsList, mockTasksList, mockExistsSync, mockReadFileSync } = vi.hoisted(() => ({
  mockTasklistsList: vi.fn(),
  mockTasksList: vi.fn(),
  mockExistsSync: vi.fn(),
  mockReadFileSync: vi.fn(),
}));

vi.mock('googleapis', () => ({
  google: {
    auth: {
      OAuth2: vi.fn().mockImplementation(() => ({
        setCredentials: vi.fn(),
      })),
    },
    tasks: vi.fn().mockReturnValue({
      tasklists: {
        list: mockTasklistsList,
      },
      tasks: {
        list: mockTasksList,
      },
    }),
  },
}));

vi.mock('fs', async () => {
  const actual = await vi.importActual<typeof import('fs')>('fs');
  return {
    ...actual,
    existsSync: mockExistsSync,
    readFileSync: mockReadFileSync,
  };
});

import { createGtasksTools } from './gtasks.js';

// ── Test config ─────────────────────────────────────────────────────────────

const testConfig: NonNullable<GtasksConfig> = {
  enabled: true,
  credentials_file: '/tmp/test-creds.json',
  token_file: '/tmp/test-token.json',
};

const fakeCredentials = {
  installed: {
    client_id: 'test-client-id',
    client_secret: 'test-client-secret',
    redirect_uris: ['http://localhost'],
  },
};

const fakeToken = {
  access_token: 'test-access-token',
  refresh_token: 'test-refresh-token',
};

// ── Helpers ─────────────────────────────────────────────────────────────────

function setupValidAuth() {
  mockExistsSync.mockReturnValue(true);
  mockReadFileSync.mockImplementation((path: unknown) => {
    const p = String(path);
    if (p.includes('creds')) {return JSON.stringify(fakeCredentials);}
    if (p.includes('token')) {return JSON.stringify(fakeToken);}
    return '';
  });
}

// ═════════════════════════════════════════════════════════════════════════════

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

describe('createGtasksTools', () => {
  it('returns 2 tools with correct names', () => {
    const tools = createGtasksTools(testConfig);
    expect(tools).toHaveLength(2);
    expect(tools.map(t => t.name)).toEqual(['tasks.lists', 'tasks.list']);
  });

  it('tools have descriptions and input schemas', () => {
    const tools = createGtasksTools(testConfig);
    for (const tool of tools) {
      expect(tool.description).toBeTruthy();
      expect(tool.inputSchema).toBeDefined();
      expect(tool.inputSchema.type).toBe('object');
    }
  });
});

describe('tasks.lists', () => {
  it('returns error when credentials file missing', async () => {
    mockExistsSync.mockReturnValue(false);
    const [listsTool] = createGtasksTools(testConfig);

    const result = await listsTool.execute({});

    expect(result.success).toBe(false);
    expect(result.error).toContain('Credentials file not found');
  });

  it('returns error when token file missing', async () => {
    mockExistsSync.mockImplementation((path: unknown) => {
      return String(path).includes('creds');
    });
    mockReadFileSync.mockReturnValue(JSON.stringify(fakeCredentials));
    const [listsTool] = createGtasksTools(testConfig);

    const result = await listsTool.execute({});

    expect(result.success).toBe(false);
    expect(result.error).toContain('Token file not found');
  });

  it('lists task lists', async () => {
    setupValidAuth();
    mockTasklistsList.mockResolvedValue({
      data: {
        items: [
          { id: 'list1', title: 'My Tasks', updated: '2026-02-10T10:00:00Z' },
          { id: 'list2', title: 'Work', updated: '2026-02-09T15:00:00Z' },
        ],
      },
    });

    const [listsTool] = createGtasksTools(testConfig);
    const result = await listsTool.execute({});

    expect(result.success).toBe(true);
    expect(result.output).toContain('My Tasks');
    expect(result.output).toContain('Work');
    expect(result.output).toContain('list1');
    expect(result.output).toContain('list2');
  });

  it('handles empty results', async () => {
    setupValidAuth();
    mockTasklistsList.mockResolvedValue({ data: { items: [] } });

    const [listsTool] = createGtasksTools(testConfig);
    const result = await listsTool.execute({});

    expect(result.success).toBe(true);
    expect(result.output).toBe('No task lists found.');
  });

  it('respects maxResults parameter', async () => {
    setupValidAuth();
    mockTasklistsList.mockResolvedValue({ data: { items: [] } });

    const [listsTool] = createGtasksTools(testConfig);
    await listsTool.execute({ maxResults: 5 });

    expect(mockTasklistsList).toHaveBeenCalledWith(
      expect.objectContaining({ maxResults: 5 }),
    );
  });

  it('handles API errors gracefully', async () => {
    setupValidAuth();
    mockTasklistsList.mockRejectedValue(new Error('API quota exceeded'));

    const [listsTool] = createGtasksTools(testConfig);
    const result = await listsTool.execute({});

    expect(result.success).toBe(false);
    expect(result.error).toContain('API quota exceeded');
  });
});

describe('tasks.list', () => {
  it('lists tasks from default list', async () => {
    setupValidAuth();
    mockTasksList.mockResolvedValue({
      data: {
        items: [
          { id: 'task1', title: 'Buy groceries', status: 'needsAction', due: '2026-02-11T00:00:00Z', notes: 'Milk, bread', parent: '' },
          { id: 'task2', title: 'Call dentist', status: 'completed', due: '', notes: '', parent: '' },
        ],
      },
    });

    const [, listTool] = createGtasksTools(testConfig);
    const result = await listTool.execute({});

    expect(result.success).toBe(true);
    expect(result.output).toContain('[ ] Buy groceries');
    expect(result.output).toContain('Due: 2026-02-11');
    expect(result.output).toContain('Notes: Milk, bread');
    expect(result.output).toContain('[x] Call dentist');

    expect(mockTasksList).toHaveBeenCalledWith(
      expect.objectContaining({
        tasklist: '@default',
        showCompleted: true,
        showHidden: false,
      }),
    );
  });

  it('uses specified taskListId', async () => {
    setupValidAuth();
    mockTasksList.mockResolvedValue({ data: { items: [] } });

    const [, listTool] = createGtasksTools(testConfig);
    await listTool.execute({ taskListId: 'list123' });

    expect(mockTasksList).toHaveBeenCalledWith(
      expect.objectContaining({ tasklist: 'list123' }),
    );
  });

  it('respects showCompleted parameter', async () => {
    setupValidAuth();
    mockTasksList.mockResolvedValue({ data: { items: [] } });

    const [, listTool] = createGtasksTools(testConfig);
    await listTool.execute({ showCompleted: false });

    expect(mockTasksList).toHaveBeenCalledWith(
      expect.objectContaining({ showCompleted: false }),
    );
  });

  it('handles empty results', async () => {
    setupValidAuth();
    mockTasksList.mockResolvedValue({ data: { items: [] } });

    const [, listTool] = createGtasksTools(testConfig);
    const result = await listTool.execute({});

    expect(result.success).toBe(true);
    expect(result.output).toBe('No tasks found.');
  });

  it('returns error when credentials missing', async () => {
    mockExistsSync.mockReturnValue(false);
    const [, listTool] = createGtasksTools(testConfig);

    const result = await listTool.execute({});

    expect(result.success).toBe(false);
    expect(result.error).toContain('Credentials file not found');
  });

  it('handles API errors gracefully', async () => {
    setupValidAuth();
    mockTasksList.mockRejectedValue(new Error('Not Found'));

    const [, listTool] = createGtasksTools(testConfig);
    const result = await listTool.execute({});

    expect(result.success).toBe(false);
    expect(result.error).toContain('Not Found');
  });

  it('respects maxResults parameter', async () => {
    setupValidAuth();
    mockTasksList.mockResolvedValue({ data: { items: [] } });

    const [, listTool] = createGtasksTools(testConfig);
    await listTool.execute({ maxResults: 50 });

    expect(mockTasksList).toHaveBeenCalledWith(
      expect.objectContaining({ maxResults: 50 }),
    );
  });
});
