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

// Hoisted mocks so vi.mock factories can reference them
const { mockFilesList, mockFilesGet, mockFilesExport, mockExistsSync, mockReadFileSync } = vi.hoisted(() => ({
  mockFilesList: vi.fn(),
  mockFilesGet: vi.fn(),
  mockFilesExport: 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,
        get: mockFilesGet,
        export: mockFilesExport,
      },
    }),
  },
}));

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

import { createGdriveTools } from './gdrive.js';

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

const testConfig: NonNullable<GdriveConfig> = {
  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,
  mimeType: string,
  opts?: { modifiedTime?: string; size?: string; owners?: string[]; webViewLink?: string },
) {
  return {
    id,
    name,
    mimeType,
    modifiedTime: opts?.modifiedTime ?? '2026-02-10T12:00:00Z',
    size: opts?.size,
    owners: (opts?.owners ?? ['owner@test.com']).map(email => ({ emailAddress: email })),
    webViewLink: opts?.webViewLink ?? `https://drive.google.com/file/d/${id}/view`,
  };
}

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

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

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

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

describe('drive.list', () => {
  it('returns error when credentials file missing', async () => {
    mockExistsSync.mockReturnValue(false);
    const [listTool] = createGdriveTools(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] = createGdriveTools(testConfig);

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

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

  it('lists recent files', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({
      data: {
        files: [
          mockDriveFile('f1', 'Report.pdf', 'application/pdf', { size: '1048576' }),
          mockDriveFile('f2', 'Notes', 'application/vnd.google-apps.document'),
        ],
      },
    });

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

    expect(result.success).toBe(true);
    expect(result.output).toContain('Report.pdf');
    expect(result.output).toContain('PDF');
    expect(result.output).toContain('1.0 MB');
    expect(result.output).toContain('Notes');
    expect(result.output).toContain('Google Doc');

    expect(mockFilesList).toHaveBeenCalledWith(
      expect.objectContaining({
        q: 'trashed=false',
        pageSize: 10,
        orderBy: 'modifiedTime desc',
      }),
    );
  });

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

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

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

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

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

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

  it('filters by mimeType', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({ data: { files: [] } });

    const [listTool] = createGdriveTools(testConfig);
    await listTool.execute({ mimeType: 'application/pdf' });

    expect(mockFilesList).toHaveBeenCalledWith(
      expect.objectContaining({
        q: "trashed=false and mimeType='application/pdf'",
      }),
    );
  });

  it('filters by folderId', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({ data: { files: [] } });

    const [listTool] = createGdriveTools(testConfig);
    await listTool.execute({ folderId: 'folder123' });

    expect(mockFilesList).toHaveBeenCalledWith(
      expect.objectContaining({
        q: "trashed=false and 'folder123' in parents",
      }),
    );
  });

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

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

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

describe('drive.search', () => {
  it('searches with query parameter', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({
      data: {
        files: [
          mockDriveFile('f1', 'Q1 Budget', 'application/vnd.google-apps.spreadsheet'),
        ],
      },
    });

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

    expect(result.success).toBe(true);
    expect(result.output).toContain('Q1 Budget');
    expect(result.output).toContain('Google Sheet');

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

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

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

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

  it('filters by mimeType', async () => {
    setupValidAuth();
    mockFilesList.mockResolvedValue({ data: { files: [] } });

    const [, searchTool] = createGdriveTools(testConfig);
    await searchTool.execute({ query: 'report', mimeType: 'application/pdf' });

    expect(mockFilesList).toHaveBeenCalledWith(
      expect.objectContaining({
        q: expect.stringContaining("mimeType='application/pdf'"),
      }),
    );
  });

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

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

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

describe('drive.read', () => {
  it('exports Google Docs as plain text', async () => {
    setupValidAuth();
    mockFilesGet.mockResolvedValue({
      data: { id: 'doc1', name: 'My Doc', mimeType: 'application/vnd.google-apps.document', size: null },
    });
    mockFilesExport.mockResolvedValue({ data: 'Hello from the document' });

    const [, , readTool] = createGdriveTools(testConfig);
    const result = await readTool.execute({ fileId: 'doc1' });

    expect(result.success).toBe(true);
    expect(result.output).toContain('Name: My Doc');
    expect(result.output).toContain('Type: Google Doc');
    expect(result.output).toContain('Hello from the document');

    expect(mockFilesExport).toHaveBeenCalledWith(
      { fileId: 'doc1', mimeType: 'text/plain' },
      { responseType: 'text' },
    );
  });

  it('exports Google Sheets as CSV', async () => {
    setupValidAuth();
    mockFilesGet.mockResolvedValue({
      data: { id: 'sheet1', name: 'Budget', mimeType: 'application/vnd.google-apps.spreadsheet' },
    });
    mockFilesExport.mockResolvedValue({ data: 'Name,Amount\nAlice,100' });

    const [, , readTool] = createGdriveTools(testConfig);
    const result = await readTool.execute({ fileId: 'sheet1' });

    expect(result.success).toBe(true);
    expect(result.output).toContain('Name: Budget');
    expect(result.output).toContain('Type: Google Sheet');
    expect(result.output).toContain('Name,Amount');

    expect(mockFilesExport).toHaveBeenCalledWith(
      { fileId: 'sheet1', mimeType: 'text/csv' },
      { responseType: 'text' },
    );
  });

  it('downloads plain text files directly', async () => {
    setupValidAuth();
    mockFilesGet
      .mockResolvedValueOnce({
        data: { id: 'txt1', name: 'notes.txt', mimeType: 'text/plain', size: '42' },
      })
      .mockResolvedValueOnce({ data: 'These are my notes' });

    const [, , readTool] = createGdriveTools(testConfig);
    const result = await readTool.execute({ fileId: 'txt1' });

    expect(result.success).toBe(true);
    expect(result.output).toContain('Name: notes.txt');
    expect(result.output).toContain('These are my notes');
  });

  it('reports binary files as unreadable', async () => {
    setupValidAuth();
    mockFilesGet.mockResolvedValue({
      data: { id: 'img1', name: 'photo.jpg', mimeType: 'image/jpeg', size: '500000' },
    });

    const [, , readTool] = createGdriveTools(testConfig);
    const result = await readTool.execute({ fileId: 'img1' });

    expect(result.success).toBe(true);
    expect(result.output).toContain('Name: photo.jpg');
    expect(result.output).toContain('Binary file');
  });

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

    const result = await readTool.execute({ fileId: 'doc1' });

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

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

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

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