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

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

vi.mock('googleapis', () => ({
  google: {
    auth: {
      OAuth2: vi.fn().mockImplementation(() => ({
        setCredentials: vi.fn(),
      })),
    },
    drive: vi.fn().mockReturnValue({
      files: {
        list: mockFilesList,
      },
    }),
    docs: vi.fn().mockReturnValue({
      documents: {
        get: mockDocumentsGet,
      },
    }),
  },
}));

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

import { createGdocsTools } from './gdocs.js';

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

const testConfig: NonNullable<GdocsConfig> = {
  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 '';
  });
}

function mockDriveFile(
  id: string,
  name: string,
  opts?: { modifiedTime?: string; owners?: string[]; webViewLink?: string },
) {
  return {
    id,
    name,
    modifiedTime: opts?.modifiedTime ?? '2026-02-10T12:00:00Z',
    owners: (opts?.owners ?? ['owner@test.com']).map(email => ({ emailAddress: email })),
    webViewLink: opts?.webViewLink ?? `https://docs.google.com/document/d/${id}/edit`,
  };
}

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

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

describe('createGdocsTools', () => {
  it('returns 3 tools with correct names', () => {
    const tools = createGdocsTools(testConfig);
    expect(tools).toHaveLength(3);
    expect(tools.map(t => t.name)).toEqual(['docs.list', 'docs.search', 'docs.read']);
  });

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

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

    const result = await listTool.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 [listTool] = createGdocsTools(testConfig);

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

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

  it('lists recent documents', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({
      data: {
        files: [
          mockDriveFile('doc1', 'Project Plan'),
          mockDriveFile('doc2', 'Meeting Notes'),
        ],
      },
    });

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

    expect(result.success).toBe(true);
    expect(result.output).toContain('Project Plan');
    expect(result.output).toContain('Meeting Notes');
    expect(result.output).toContain('doc1');
    expect(result.output).toContain('doc2');

    expect(mockFilesList).toHaveBeenCalledWith(
      expect.objectContaining({
        q: "mimeType='application/vnd.google-apps.document' and trashed=false",
        pageSize: 10,
        orderBy: 'modifiedTime desc',
      }),
    );
  });

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

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

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

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

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

    expect(mockFilesList).toHaveBeenCalledWith(
      expect.objectContaining({
        pageSize: 5,
      }),
    );
  });

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

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

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

describe('docs.search', () => {
  it('searches with query parameter', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({
      data: {
        files: [
          mockDriveFile('doc1', 'Sprint Planning Doc'),
        ],
      },
    });

    const [, searchTool] = createGdocsTools(testConfig);
    const result = await searchTool.execute({ query: 'planning' });

    expect(result.success).toBe(true);
    expect(result.output).toContain('Sprint Planning Doc');

    expect(mockFilesList).toHaveBeenCalledWith(
      expect.objectContaining({
        q: expect.stringContaining("name contains 'planning'"),
      }),
    );
  });

  it('escapes single quotes in query', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({ data: { files: [] } });

    const [, searchTool] = createGdocsTools(testConfig);
    await searchTool.execute({ query: "it's a test" });

    expect(mockFilesList).toHaveBeenCalledWith(
      expect.objectContaining({
        q: expect.stringContaining("it\\'s a test"),
      }),
    );
  });

  it('respects maxResults param', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({ data: { files: [] } });

    const [, searchTool] = createGdocsTools(testConfig);
    await searchTool.execute({ query: 'meeting', maxResults: 3 });

    expect(mockFilesList).toHaveBeenCalledWith(
      expect.objectContaining({
        pageSize: 3,
      }),
    );
  });

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

    const result = await searchTool.execute({ query: 'test' });

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

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

    const [, searchTool] = createGdocsTools(testConfig);
    const result = await searchTool.execute({ query: 'test' });

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

describe('docs.read', () => {
  it('reads document content', async () => {
    setupValidAuth();
    mockDocumentsGet.mockResolvedValue({
      data: {
        title: 'My Document',
        body: {
          content: [
            {
              paragraph: {
                elements: [
                  { textRun: { content: 'Hello ' } },
                  { textRun: { content: 'World\n' } },
                ],
              },
            },
            {
              paragraph: {
                elements: [
                  { textRun: { content: 'Second paragraph\n' } },
                ],
              },
            },
          ],
        },
      },
    });

    const [, , readTool] = createGdocsTools(testConfig);
    const result = await readTool.execute({ documentId: 'doc123' });

    expect(result.success).toBe(true);
    expect(result.output).toContain('Title: My Document');
    expect(result.output).toContain('Hello World');
    expect(result.output).toContain('Second paragraph');

    expect(mockDocumentsGet).toHaveBeenCalledWith({
      documentId: 'doc123',
    });
  });

  it('handles empty document', async () => {
    setupValidAuth();
    mockDocumentsGet.mockResolvedValue({
      data: {
        title: 'Empty Doc',
        body: { content: [] },
      },
    });

    const [, , readTool] = createGdocsTools(testConfig);
    const result = await readTool.execute({ documentId: 'doc-empty' });

    expect(result.success).toBe(true);
    expect(result.output).toContain('Title: Empty Doc');
    expect(result.output).toContain('(empty document)');
  });

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

    const result = await readTool.execute({ documentId: 'doc123' });

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

  it('handles API errors gracefully', async () => {
    setupValidAuth();
    mockDocumentsGet.mockRejectedValue(new Error('Document not found'));

    const [, , readTool] = createGdocsTools(testConfig);
    const result = await readTool.execute({ documentId: 'nonexistent' });

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

  it('formats output with owners and links', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({
      data: {
        files: [
          mockDriveFile('doc1', 'Team Wiki', {
            modifiedTime: '2026-02-09T08:30:00Z',
            owners: ['alice@test.com'],
            webViewLink: 'https://docs.google.com/document/d/doc1/edit',
          }),
        ],
      },
    });

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

    expect(result.success).toBe(true);
    expect(result.output).toContain('Team Wiki');
    expect(result.output).toContain('2026-02-09T08:30:00Z');
    expect(result.output).toContain('alice@test.com');
    expect(result.output).toContain('https://docs.google.com/document/d/doc1/edit');
  });
});
