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

// Mock all I/O dependencies before importing handleToolCall
vi.mock('./billing/usage.js', () => ({
  recordUsage: vi.fn().mockResolvedValue(undefined),
  checkLimit: vi.fn().mockResolvedValue({ allowed: true, limit: 1000, used: 5 }),
}));
vi.mock('./db.js', () => ({ getPool: vi.fn() }));
vi.mock('./imap.js', () => ({
  searchMessages: vi.fn().mockResolvedValue([]),
  readMessage: vi.fn().mockResolvedValue({}),
  getProfile: vi.fn().mockResolvedValue({ email: 'test@example.com' }),
  listFolders: vi.fn().mockResolvedValue([]),
}));
vi.mock('./smtp.js', () => ({
  sendEmail: vi.fn().mockResolvedValue({ messageId: 'abc' }),
  createDraft: vi.fn().mockResolvedValue({ id: '1' }),
}));
vi.mock('./clients/obsidian.js', () => ({
  searchNotes: vi.fn().mockResolvedValue([]),
  getNote: vi.fn().mockResolvedValue({ content: '' }),
  appendToNote: vi.fn().mockResolvedValue({}),
  updateNote: vi.fn().mockResolvedValue({}),
  getSyncStatus: vi.fn().mockResolvedValue({}),
}));
vi.mock('./clients/whatsapp.js', () => ({
  sendMessage: vi.fn().mockResolvedValue({}),
  sendTemplate: vi.fn().mockResolvedValue({}),
  getMessageStatus: vi.fn().mockResolvedValue({}),
  listTemplates: vi.fn().mockResolvedValue([]),
}));
vi.mock('./clients/linkedin.js', () => ({
  getProfile: vi.fn().mockResolvedValue({}),
  createPost: vi.fn().mockResolvedValue({}),
  createVideoPost: vi.fn().mockResolvedValue({}),
  searchConnections: vi.fn().mockResolvedValue([]),
  sendMessage: vi.fn().mockResolvedValue({}),
}));
vi.mock('./clients/telegram.js', () => ({
  getMe: vi.fn().mockResolvedValue({}),
  sendMessage: vi.fn().mockResolvedValue({}),
  sendPhoto: vi.fn().mockResolvedValue({}),
  getUpdates: vi.fn().mockResolvedValue([]),
  getChat: vi.fn().mockResolvedValue({}),
}));
vi.mock('./clients/discord.js', () => ({
  getMe: vi.fn().mockResolvedValue({}),
  getGuilds: vi.fn().mockResolvedValue([]),
  getChannels: vi.fn().mockResolvedValue([]),
  sendMessage: vi.fn().mockResolvedValue({}),
  getMessages: vi.fn().mockResolvedValue([]),
}));
vi.mock('./clients/instagram.js', () => ({
  getProfile: vi.fn().mockResolvedValue({}),
  getMedia: vi.fn().mockResolvedValue([]),
  createImagePost: vi.fn().mockResolvedValue({}),
  createReel: vi.fn().mockResolvedValue({}),
}));
vi.mock('./clients/twitter.js', () => ({
  searchTweets: vi.fn().mockResolvedValue([]),
  getUserProfile: vi.fn().mockResolvedValue({}),
  getUserTweets: vi.fn().mockResolvedValue([]),
  createTweet: vi.fn().mockResolvedValue({}),
  uploadVideoAndTweet: vi.fn().mockResolvedValue({}),
}));
vi.mock('./clients/tiktok.js', () => ({
  getUserProfile: vi.fn().mockResolvedValue({}),
  getCreatorInfo: vi.fn().mockResolvedValue({}),
  createVideo: vi.fn().mockResolvedValue({}),
  getVideoStatus: vi.fn().mockResolvedValue({}),
}));
vi.mock('./clients/snapchat.js', () => ({
  getMe: vi.fn().mockResolvedValue({}),
  createSnap: vi.fn().mockResolvedValue({}),
  getAdAccounts: vi.fn().mockResolvedValue([]),
}));
vi.mock('./clients/facebook.js', () => ({
  getPage: vi.fn().mockResolvedValue({}),
  getPosts: vi.fn().mockResolvedValue([]),
  createPost: vi.fn().mockResolvedValue({}),
  createPhotoPost: vi.fn().mockResolvedValue({}),
  createVideoPost: vi.fn().mockResolvedValue({}),
}));
vi.mock('./redis.js', () => ({ default: { get: vi.fn(), set: vi.fn(), del: vi.fn() } }));
vi.mock('./multitenancy/credential-store.js', () => ({
  getCredential: vi.fn().mockResolvedValue(null),
}));

import { handleToolCall, stripAccountParam, tools } from './tools.js';
import { checkLimit, recordUsage } from './billing/usage.js';

beforeEach(() => {
  vi.clearAllMocks();
  vi.mocked(checkLimit).mockResolvedValue({ allowed: true, limit: 1000, used: 5 });
});

const mockCustomer = {
  id: 'cust-123',
  plan: 'growth' as const,
  active: true,
  email: 'test@example.com',
  role: 'user',
  getCredential: vi.fn().mockResolvedValue(null),
};

describe('handleToolCall — plan limit gate', () => {

  it('returns isError when customer is over limit', async () => {
    vi.mocked(checkLimit).mockResolvedValue({ allowed: false, limit: 1000, used: 1000 });
    const result = await handleToolCall('get_profile', {}, mockCustomer);
    expect(result.isError).toBe(true);
    expect(result.content[0].text).toMatch(/limit/i);
  });

  it('does not check limit when no customer (unauthenticated dev mode)', async () => {
    await handleToolCall('get_profile', {});
    expect(checkLimit).not.toHaveBeenCalled();
  });

  it('proceeds normally when under limit', async () => {
    const result = await handleToolCall('get_profile', {}, mockCustomer);
    expect(result.isError).toBeUndefined();
    expect(checkLimit).toHaveBeenCalledWith('cust-123', 'growth');
  });
});

describe('handleToolCall — platform attribution', () => {

  it.each([
    ['send_email', 'email'],
    ['create_draft', 'email'],
    ['search_messages', 'email'],
    ['get_profile', 'email'],
    ['list_folders', 'email'],
    ['yahoo_send_email', 'email'],
    ['linkedin_create_post', 'linkedin'],
    ['obsidian_search_notes', 'obsidian'],
    ['tiktok_create_video', 'tiktok'],
    ['whatsapp_send_message', 'whatsapp'],
    ['telegram_send_message', 'telegram'],
    ['discord_send_message', 'discord'],
    ['instagram_create_post', 'instagram'],
    ['twitter_create_tweet', 'twitter'],
    ['snapchat_create_snap', 'snapchat'],
    ['facebook_create_post', 'facebook'],
  ])('%s → platform "%s"', async (toolName, expectedPlatform) => {
    await handleToolCall(toolName, {}, mockCustomer).catch(() => {});
    const calls = vi.mocked(recordUsage).mock.calls;
    const last = calls[calls.length - 1];
    if (last) {
      expect(last[1]).toBe(expectedPlatform);
    }
  });
});

describe('handleToolCall — error handling', () => {
  it('returns isError: true on tool exception', async () => {
    const { getProfile } = await import('./imap.js');
    vi.mocked(getProfile).mockRejectedValueOnce(new Error('IMAP connection refused'));
    const result = await handleToolCall('get_profile', {});
    expect(result.isError).toBe(true);
    expect(result.content[0].text).toContain('IMAP connection refused');
  });
});

describe('stripAccountParam', () => {
  it('removes account from inputSchema.properties', () => {
    const tool = tools.find(t => 'account' in (t.inputSchema.properties ?? {}))!;
    expect(tool).toBeDefined();
    const stripped = stripAccountParam(tool);
    expect(stripped.inputSchema.properties).not.toHaveProperty('account');
  });

  it('does not mutate the original tool', () => {
    const tool = tools.find(t => 'account' in (t.inputSchema.properties ?? {}))!;
    const before = Object.keys(tool.inputSchema.properties ?? {});
    stripAccountParam(tool);
    const after = Object.keys(tool.inputSchema.properties ?? {});
    expect(after).toEqual(before);
  });

  it('preserves all other properties', () => {
    const tool = tools.find(t => 'account' in (t.inputSchema.properties ?? {}))!;
    const stripped = stripAccountParam(tool);
    const originalWithoutAccount = Object.fromEntries(
      Object.entries(tool.inputSchema.properties ?? {}).filter(([k]) => k !== 'account')
    );
    expect(stripped.inputSchema.properties).toEqual(originalWithoutAccount);
    expect(stripped.name).toBe(tool.name);
    expect(stripped.description).toBe(tool.description);
  });

  it('handles tools with no account param safely', () => {
    const tool = tools.find(t => !('account' in (t.inputSchema.properties ?? {})))!;
    expect(tool).toBeDefined();
    const stripped = stripAccountParam(tool);
    expect(stripped.inputSchema.properties).toEqual(tool.inputSchema.properties);
  });

  it('all tools in multi-tenant mode have no account param', () => {
    const stripped = tools.map(stripAccountParam);
    for (const t of stripped) {
      expect(t.inputSchema.properties).not.toHaveProperty('account');
    }
  });
});
