/**
 * SERP Pattern Analyst - Comprehensive Test Suite
 *
 * @module @claude-flow-novice/seo-analysis/__tests__/serp-pattern-analyst
 * @description Complete test coverage for SERP pattern analysis (Phase 2 Sprint 2)
 * @version 1.0.0
 *
 * Coverage:
 * - Configuration validation
 * - API integration (mocked Google & SerpAPI)
 * - SERP feature detection
 * - Ranking pattern analysis
 * - Semantic clustering
 * - Content gap identification
 * - Recommendation generation
 * - Error handling (network failures, timeouts, rate limits)
 * - Edge cases (insufficient data, malformed responses)
 */

// FIX: Mock axios BEFORE importing SERPPatternAnalyst (Jest hoisting requirement)
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

import { SERPPatternAnalyst } from '../serp-pattern-analyst';
import {
  SERPAnalysisConfig,
  SERPAnalysisError,
  SERPAnalysisErrorCode,
  SERPFeatureType,
  ContentType,
  FreshnessSignal,
  RecommendationType,
  GoogleSearchResponse,
  GoogleSearchItem,
  SerpAPIResponse,
  SerpAPIOrganicResult,
} from '../../types/serp-analysis';

// ============================================================================
// TEST HELPERS AND MOCKS
// ============================================================================

/**
 * Create mock Google search response
 * FIX: Preserve user-provided fields (title, link, snippet, etc.)
 */
function createMockGoogleResponse(
  items: Partial<GoogleSearchItem>[],
  options: { error?: { code: number; message: string } } = {}
): GoogleSearchResponse {
  if (options.error) {
    return {
      kind: 'customsearch#search',
      error: options.error,
    };
  }

  return {
    kind: 'customsearch#search',
    items: items.map(
      (item, index): GoogleSearchItem => ({
        title: item.title ?? `Result ${index + 1}`,  // ✅ Use provided or default
        link: item.link ?? `https://example${index}.com/page`,
        snippet: item.snippet ?? `Snippet for result ${index + 1}`,
        displayLink: item.displayLink ?? `example${index}.com`,
        htmlSnippet: item.htmlSnippet,
        pagemap: item.pagemap,
      })
    ),
    searchInformation: {
      totalResults: items.length.toString(),
      searchTime: 0.5,
    },
  };
}

/**
 * Create mock SerpAPI response
 * FIX: Preserve user-provided fields (position, title, link, snippet, etc.)
 */
function createMockSerpAPIResponse(
  results: Partial<SerpAPIOrganicResult>[],
  options: { error?: string } = {}
): SerpAPIResponse {
  if (options.error) {
    return {
      error: options.error,
    };
  }

  return {
    search_metadata: {
      status: 'Success',
      created_at: new Date().toISOString(),
      processed_at: new Date().toISOString(),
      total_time_taken: 0.5,
    },
    search_parameters: {
      q: 'test query',
      engine: 'google',
    },
    organic_results: results.map(
      (result, index): SerpAPIOrganicResult => ({
        position: result.position ?? index + 1,  // ✅ Use provided or default
        title: result.title ?? `Result ${index + 1}`,
        link: result.link ?? `https://example${index}.com/page`,
        snippet: result.snippet ?? `Snippet for result ${index + 1}`,
        displayed_link: result.displayed_link ?? `example${index}.com`,
        rich_snippet: result.rich_snippet,
        sitelinks: result.sitelinks,
      })
    ),
  };
}

/**
 * Setup environment for tests
 * By default uses SerpAPI only for richer test data
 */
function setupTestEnvironment(overrides: Record<string, string> = {}) {
  const env = {
    GOOGLE_API_KEY: '', // Disabled by default
    GOOGLE_SEARCH_ENGINE_ID: '', // Disabled by default
    SERPAPI_KEY: 'c8f9a3b2d1e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5',
    ...overrides,
  };

  Object.entries(env).forEach(([key, value]) => {
    process.env[key] = value;
  });

  return env;
}

/**
 * Clear test environment
 */
function clearTestEnvironment() {
  delete process.env.GOOGLE_API_KEY;
  delete process.env.GOOGLE_SEARCH_ENGINE_ID;
  delete process.env.SERPAPI_KEY;
}

// ============================================================================
// P0 CRITICAL: CONFIGURATION VALIDATION
// ============================================================================

describe('Configuration Validation', () => {
  beforeEach(() => {
    clearTestEnvironment();
  });

  describe('Keyword Validation', () => {
    it('should reject empty keyword', () => {
      setupTestEnvironment();

      expect(() => {
        new SERPPatternAnalyst({ keyword: '' });
      }).toThrow(SERPAnalysisError);

      expect(() => {
        new SERPPatternAnalyst({ keyword: '' });
      }).toThrow('Keyword must be a non-empty string');
    });

    it('should reject keyword less than 2 characters', () => {
      setupTestEnvironment();

      expect(() => {
        new SERPPatternAnalyst({ keyword: 'a' });
      }).toThrow(SERPAnalysisError);
    });

    it('should reject keyword longer than 200 characters', () => {
      setupTestEnvironment();
      const longKeyword = 'a'.repeat(201);

      expect(() => {
        new SERPPatternAnalyst({ keyword: longKeyword });
      }).toThrow(SERPAnalysisError);
    });

    it('should accept valid keyword', () => {
      setupTestEnvironment();

      expect(() => {
        new SERPPatternAnalyst({ keyword: 'test keyword' });
      }).not.toThrow();
    });

    it('should trim whitespace from keyword', () => {
      setupTestEnvironment();
      const analyst = new SERPPatternAnalyst({ keyword: '  test keyword  ' });

      expect((analyst as any).config.keyword).toBe('test keyword');
    });
  });

  describe('API Key Validation', () => {
    it('should throw error when no API keys configured', () => {
      expect(() => {
        new SERPPatternAnalyst({ keyword: 'test' });
      }).toThrow(SERPAnalysisError);

      expect(() => {
        new SERPPatternAnalyst({ keyword: 'test' });
      }).toThrow('No API keys configured');
    });

    it('should accept Google API configuration', () => {
      expect(() => {
        new SERPPatternAnalyst({
          keyword: 'test',
          googleApiKey: 'valid-google-key-1234567890',
          googleSearchEngineId: 'valid-search-engine-id',
        });
      }).not.toThrow();
    });

    it('should accept SerpAPI configuration', () => {
      expect(() => {
        new SERPPatternAnalyst({
          keyword: 'test',
          serpApiKey: 'valid-serpapi-key-1234567890',
        });
      }).not.toThrow();
    });

    it('should detect placeholder API keys and warn', () => {
      const analyst = new SERPPatternAnalyst({
        keyword: 'test',
        googleApiKey: '[REDACTED]',
        googleSearchEngineId: 'test',
        serpApiKey: 'valid-serpapi-key-1234567890',
      });

      expect((analyst as any).warnings).toContain('Google API key appears to be a placeholder');
    });

    it('should use environment variables if not provided in config', () => {
      setupTestEnvironment({
        SERPAPI_KEY: 'c8f9a3b2d1e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5',
      });

      expect(() => {
        new SERPPatternAnalyst({ keyword: 'test' });
      }).not.toThrow();
    });
  });

  describe('Config Parameter Validation', () => {
    beforeEach(() => {
      setupTestEnvironment({
        SERPAPI_KEY: 'c8f9a3b2d1e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5',
      });
    });

    it('should reject maxResults less than 5', () => {
      expect(() => {
        new SERPPatternAnalyst({ keyword: 'test', maxResults: 3 });
      }).toThrow('maxResults must be between 5 and 100');
    });

    it('should reject maxResults greater than 100', () => {
      expect(() => {
        new SERPPatternAnalyst({ keyword: 'test', maxResults: 101 });
      }).toThrow('maxResults must be between 5 and 100');
    });

    it('should accept valid maxResults', () => {
      expect(() => {
        new SERPPatternAnalyst({ keyword: 'test', maxResults: 10 });
      }).not.toThrow();
    });

    it('should use default configuration values', () => {
      const analyst = new SERPPatternAnalyst({ keyword: 'test' });
      const config = (analyst as any).config;

      expect(config.maxResults).toBe(10);
      expect(config.enableContentScraping).toBe(false);
      expect(config.requestTimeoutMs).toBe(30000);
      expect(config.verbose).toBe(false);
      expect(config.rateLimitMs).toBe(1000);
    });
  });
});

// ============================================================================
// P0 CRITICAL: GOOGLE CUSTOM SEARCH INTEGRATION
// ============================================================================

describe('Google Custom Search Integration', () => {
  beforeEach(() => {
    setupTestEnvironment({
      GOOGLE_API_KEY: 'AIzaSyB3k9m8nL2pQ5rT7uV9wX0yZ1aC4dE6fG8hI0',
      GOOGLE_SEARCH_ENGINE_ID: 'a1b2c3d4e5f6g7h8i9j0',
      SERPAPI_KEY: 'c8f9a3b2d1e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5', // Fallback
    });
    jest.clearAllMocks();
  });

  it('should fetch and parse Google search results', async () => {
    const mockResponse = createMockGoogleResponse([
      {
        title: 'Best Running Shoes 2024',
        link: 'https://example.com/running-shoes-2024',
        snippet: 'Comprehensive guide to the best running shoes in 2024',
      },
      {
        title: 'Top 10 Running Shoes',
        link: 'https://example2.com/top-running-shoes',
        snippet: 'Our top picks for running shoes this year',
      },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({
      keyword: 'best running shoes',
    });

    const result = await analyst.analyze();

    expect(mockedAxios.get).toHaveBeenCalledWith(
      'https://www.googleapis.com/customsearch/v1',
      expect.objectContaining({
        params: expect.objectContaining({
          q: 'best running shoes',
        }),
      })
    );

    expect(result.results).toHaveLength(2);
    expect(result.results[0].title).toBe('Best Running Shoes 2024');
    expect(result.results[0].position).toBe(1);
  });

  it('should handle Google API errors gracefully', async () => {
    const mockResponse = createMockGoogleResponse([], {
      error: { code: 400, message: 'Invalid API key' },
    });

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({
      keyword: 'test',
      googleApiKey: 'AIzaInvalidKeyButLongEnough1234567890ABC',
      googleSearchEngineId: 'a1b2c3d4e5f6g7h8i9j0',
      serpApiKey: 'c8f9a3b2d1e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5',
    });

    // Should fallback to SerpAPI
    const serpApiResponse = createMockSerpAPIResponse([
      { title: 'Fallback Result', link: 'https://example.com/fallback' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: serpApiResponse });

    const result = await analyst.analyze();

    expect(result.results).toHaveLength(1);
    expect(result.warnings).toContain('Google Custom Search failed');
  });

  it('should handle rate limit errors', async () => {
    // Explicitly clear environment variables to test error paths
    delete process.env.GOOGLE_API_KEY;
    delete process.env.GOOGLE_SEARCH_ENGINE_ID;
    delete process.env.SERPAPI_KEY;

    const error: any = new Error('Request failed with status code 429');
    error.isAxiosError = true;
    error.response = { status: 429 };
    error.code = undefined;

    // Mock axios.isAxiosError to recognize our mock error
    jest.spyOn(axios, 'isAxiosError').mockReturnValueOnce(true);
    mockedAxios.get.mockRejectedValueOnce(error);

    const analyst = new SERPPatternAnalyst({
      keyword: 'test',
      googleApiKey: 'AIzaSyB3k9m8nL2pQ5rT7uV9wX0yZ1aC4dE6fG8hI0',
      googleSearchEngineId: 'a1b2c3d4e5f6g7h8i9j0',
      serpApiKey: undefined, // Force only Google API
    });

    await expect(analyst.analyze()).rejects.toThrow('rate limit exceeded');
  });

  it('should handle timeout errors', async () => {
    // Explicitly clear environment variables to test error paths
    delete process.env.GOOGLE_API_KEY;
    delete process.env.GOOGLE_SEARCH_ENGINE_ID;
    delete process.env.SERPAPI_KEY;

    const error: any = new Error('timeout of 30000ms exceeded');
    error.isAxiosError = true;
    error.code = 'ECONNABORTED';

    // Mock axios.isAxiosError to recognize our mock error
    jest.spyOn(axios, 'isAxiosError').mockReturnValueOnce(true);
    mockedAxios.get.mockRejectedValueOnce(error);

    const analyst = new SERPPatternAnalyst({
      keyword: 'test',
      googleApiKey: 'AIzaSyB3k9m8nL2pQ5rT7uV9wX0yZ1aC4dE6fG8hI0',
      googleSearchEngineId: 'a1b2c3d4e5f6g7h8i9j0',
      serpApiKey: undefined, // Force only Google API
    });

    await expect(analyst.analyze()).rejects.toThrow('timeout');
  });
});

// ============================================================================
// P0 CRITICAL: SERPAPI INTEGRATION
// ============================================================================

describe('SerpAPI Integration', () => {
  beforeEach(() => {
    setupTestEnvironment({ GOOGLE_API_KEY: '', GOOGLE_SEARCH_ENGINE_ID: '' });
    jest.clearAllMocks();
  });

  it('should fetch and parse SerpAPI results', async () => {
    const mockResponse = createMockSerpAPIResponse([
      {
        position: 1,
        title: 'Best Running Shoes 2024',
        link: 'https://example.com/running-shoes-2024',
        snippet: 'Comprehensive guide to the best running shoes in 2024',
        sitelinks: [{ title: 'Link 1', link: 'https://example.com/link1' }],
      },
      {
        position: 2,
        title: 'Top 10 Running Shoes',
        link: 'https://example2.com/top-running-shoes',
        snippet: 'Our top picks for running shoes this year',
      },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({
      keyword: 'best running shoes',
    });

    const result = await analyst.analyze();

    expect(mockedAxios.get).toHaveBeenCalledWith(
      'https://serpapi.com/search',
      expect.objectContaining({
        params: expect.objectContaining({
          q: 'best running shoes',
          engine: 'google',
        }),
      })
    );

    expect(result.results).toHaveLength(2);
    expect(result.results[0].hasSiteLinks).toBe(true);
    expect(result.results[1].hasSiteLinks).toBe(false);
  });

  it('should handle SerpAPI errors', async () => {
    const mockResponse = createMockSerpAPIResponse([], { error: 'Invalid API key' });

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({
      keyword: 'test',
    });

    await expect(analyst.analyze()).rejects.toThrow('SerpAPI returned no results');
  });

  it('should handle SerpAPI rate limits', async () => {
    mockedAxios.get.mockRejectedValueOnce({
      isAxiosError: true,
      response: { status: 429 },
      code: undefined,
    });

    const analyst = new SERPPatternAnalyst({
      keyword: 'test',
    });

    await expect(analyst.analyze()).rejects.toThrow('SerpAPI rate limit exceeded');
  });
});

// ============================================================================
// P1 HIGH: SERP FEATURE DETECTION
// ============================================================================

describe('SERP Feature Detection', () => {
  beforeEach(() => {
    setupTestEnvironment();
    jest.clearAllMocks();
  });

  it('should detect site links feature', async () => {
    const mockResponse = createMockSerpAPIResponse([
      {
        position: 1,
        title: 'Homepage',
        link: 'https://example.com',
        snippet: 'Main site',
        sitelinks: [
          { title: 'About', link: 'https://example.com/about' },
          { title: 'Contact', link: 'https://example.com/contact' },
        ],
      },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    const siteLinkFeature = result.features.find((f) => f.type === SERPFeatureType.SITE_LINKS);
    expect(siteLinkFeature).toBeDefined();
    expect(siteLinkFeature?.confidence).toBeGreaterThan(0.9);
  });

  it('should detect video carousel pattern', async () => {
    const mockResponse = createMockSerpAPIResponse([
      {
        position: 1,
        title: 'Video Tutorial 1',
        link: 'https://youtube.com/watch?v=1',
        snippet: 'Learn how to...',
      },
      {
        position: 2,
        title: 'Video Guide 2',
        link: 'https://youtube.com/watch?v=2',
        snippet: 'Step by step...',
      },
      {
        position: 3,
        title: 'Video Course 3',
        link: 'https://vimeo.com/video/3',
        snippet: 'Complete course...',
      },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'how to cook pasta' });
    const result = await analyst.analyze();

    const videoFeature = result.features.find((f) => f.type === SERPFeatureType.VIDEO_CAROUSEL);
    expect(videoFeature).toBeDefined();
  });

  it('should warn about limited feature detection', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { position: 1, title: 'Result 1', link: 'https://example.com', snippet: 'Snippet 1' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    expect(result.warnings).toContain('Limited SERP feature detection without full HTML access');
  });
});

// ============================================================================
// P1 HIGH: RANKING PATTERN ANALYSIS
// ============================================================================

describe('Ranking Pattern Analysis', () => {
  beforeEach(() => {
    setupTestEnvironment();
    jest.clearAllMocks();
  });

  it('should analyze title and meta patterns', async () => {
    const mockResponse = createMockSerpAPIResponse([
      {
        position: 1,
        title: 'Best Running Shoes 2024 - Complete Guide',
        link: 'https://example.com/running-shoes',
        snippet: 'Discover the best running shoes for 2024',
      },
      {
        position: 2,
        title: 'Running Shoes 2024: Top 10 Picks',
        link: 'https://example2.com/shoes',
        snippet: 'Our expert picks for running shoes this year',
      },
      {
        position: 3,
        title: '2024 Running Shoe Buying Guide',
        link: 'https://example3.com/guide',
        snippet: 'Everything you need to know about buying running shoes',
      },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'running shoes' });
    const result = await analyst.analyze();

    expect(result.rankingPatterns.titleMeta).toBeDefined();
    expect(result.rankingPatterns.titleMeta.avgTitleLength).toBeGreaterThan(0);
    expect(result.rankingPatterns.titleMeta.keywordPlacement.inTitle).toBeGreaterThan(0);
  });

  it('should identify common title structures', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Guide | Brand Name', link: 'https://example1.com', snippet: 'Text' },
      { title: 'Tutorial | Brand', link: 'https://example2.com', snippet: 'Text' },
      { title: 'Article | Company', link: 'https://example3.com', snippet: 'Text' },
      { title: 'Post - Website', link: 'https://example4.com', snippet: 'Text' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    expect(result.rankingPatterns.titleMeta.commonTitlePatterns).toContain('Title | Brand');
  });

  it('should analyze URL structure patterns', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { link: 'https://example.com/blog/2024/article-1', snippet: 'Text' },
      { link: 'https://example.com/blog/2024/article-2', snippet: 'Text' },
      { link: 'https://example.com/guides/how-to-guide', snippet: 'Text' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    expect(result.rankingPatterns.urlStructure.patterns).toBeDefined();
    expect(result.rankingPatterns.urlStructure.avgUrlLength).toBeGreaterThan(0);
  });

  it('should detect freshness signals', async () => {
    const mockResponse = createMockSerpAPIResponse([
      {
        title: 'Best Gadgets 2024',
        link: 'https://example.com/blog/2024/gadgets',
        snippet: 'Latest gadgets',
      },
      {
        title: 'Gadgets Guide January 2024',
        link: 'https://example2.com/2024-01-15/guide',
        snippet: 'Updated guide',
      },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'best gadgets' });
    const result = await analyst.analyze();

    expect(result.rankingPatterns.freshnessSignals).toBeDefined();
    expect(
      result.rankingPatterns.freshnessSignals.some((s) => s.signal === FreshnessSignal.DATE_IN_TITLE)
    ).toBe(true);
  });

  it('should classify content types', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { link: 'https://example.com/blog/article', snippet: 'Blog post' },
      { link: 'https://shop.com/product/item', snippet: 'Buy now for $99' },
      { link: 'https://guide.com/how-to-guide', snippet: 'Complete tutorial guide' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    expect(result.rankingPatterns.contentTypes).toBeDefined();
    expect(result.rankingPatterns.contentTypes.length).toBeGreaterThan(0);
    expect(
      result.rankingPatterns.contentTypes.some((ct) => ct.type === ContentType.BLOG)
    ).toBe(true);
  });
});

// ============================================================================
// P1 HIGH: SEMANTIC CLUSTERING
// ============================================================================

describe('Semantic Clustering', () => {
  beforeEach(() => {
    setupTestEnvironment();
    jest.clearAllMocks();
  });

  it('should extract semantic clusters from results', async () => {
    const mockResponse = createMockSerpAPIResponse([
      {
        title: 'Running Shoe Performance Guide',
        snippet: 'Learn about running shoe performance and comfort features',
      },
      {
        title: 'Best Running Shoes for Performance',
        snippet: 'Top performance running shoes with comfort technology',
      },
      {
        title: 'Comfortable Running Shoes',
        snippet: 'Find comfortable running shoes for daily performance',
      },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'running shoes' });
    const result = await analyst.analyze();

    expect(result.semanticClusters).toBeDefined();
    expect(result.semanticClusters.length).toBeGreaterThan(0);

    // Should identify "performance" and "comfortable" as clusters
    const performanceCluster = result.semanticClusters.find((c) =>
      c.mainTopic.includes('performance')
    );
    expect(performanceCluster).toBeDefined();
  });

  it('should calculate cluster prevalence correctly', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Guide', snippet: 'guide keyword guide' },
      { title: 'Tutorial', snippet: 'tutorial content' },
      { title: 'Guide', snippet: 'another guide' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    const guideCluster = result.semanticClusters.find((c) => c.mainTopic.includes('guide'));
    if (guideCluster) {
      expect(guideCluster.prevalence).toBeGreaterThan(0.5); // Should be > 50%
    }
  });
});

// ============================================================================
// P1 HIGH: CONTENT GAP IDENTIFICATION
// ============================================================================

describe('Content Gap Identification', () => {
  beforeEach(() => {
    setupTestEnvironment();
    jest.clearAllMocks();
  });

  it('should identify missing content types', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { link: 'https://example.com/product/1', snippet: 'Buy product' },
      { link: 'https://example.com/product/2', snippet: 'Shop now' },
      { link: 'https://example.com/product/3', snippet: 'Purchase here' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'best laptops' });
    const result = await analyst.analyze();

    expect(result.contentGaps).toBeDefined();

    // Should identify blog content gap
    const blogGap = result.contentGaps.find((g) => g.topic.includes('blog'));
    expect(blogGap).toBeDefined();
  });

  it('should identify comprehensive guide gaps', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Article 1', snippet: 'Short article' },
      { title: 'Article 2', snippet: 'Another short article' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    const guideGap = result.contentGaps.find((g) => g.topic.includes('guide'));
    expect(guideGap).toBeDefined();
    expect(guideGap?.recommendedContentType).toBe(ContentType.GUIDE);
  });

  it('should prioritize content gaps correctly', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Result', snippet: 'Text' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    const highPriorityGaps = result.contentGaps.filter((g) => g.priority === 'high');
    expect(highPriorityGaps.length).toBeGreaterThanOrEqual(0);
  });
});

// ============================================================================
// P1 HIGH: RECOMMENDATION GENERATION
// ============================================================================

describe('Recommendation Generation', () => {
  beforeEach(() => {
    setupTestEnvironment();
    jest.clearAllMocks();
  });

  it('should generate title optimization recommendations', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Best Running Shoes 2024', snippet: 'Guide to running shoes' },
      { title: 'Running Shoes 2024 Guide', snippet: 'Complete running shoes guide' },
      { title: '2024 Best Running Shoes', snippet: 'Top running shoes for 2024' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'running shoes' });
    const result = await analyst.analyze();

    const titleRec = result.recommendations.find((r) => r.title.includes('title'));
    expect(titleRec).toBeDefined();
    expect(titleRec?.impact).toBeDefined();
    expect(titleRec?.effort).toBeDefined();
    expect(titleRec?.actionSteps).toBeDefined();
    expect(titleRec?.actionSteps.length).toBeGreaterThan(0);
  });

  it('should generate content length recommendations', async () => {
    const mockResponse = createMockSerpAPIResponse(
      Array(5).fill(null).map((_, i) => ({
        title: `Article ${i + 1}`,
        snippet: 'Comprehensive long-form article content',
      }))
    );

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    const lengthRec = result.recommendations.find((r) => r.title.includes('content length'));
    expect(lengthRec).toBeDefined();
    expect(lengthRec?.type).toBe(RecommendationType.CONTENT_STRUCTURE);
  });

  it('should generate SERP feature targeting recommendations', async () => {
    const mockResponse = createMockSerpAPIResponse([
      {
        position: 1,
        title: 'Homepage',
        link: 'https://example.com',
        snippet: 'Main site',
        sitelinks: [{ title: 'About', link: 'https://example.com/about' }],
      },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    const featureRec = result.recommendations.find((r) =>
      r.type === RecommendationType.SERP_FEATURE
    );
    expect(featureRec).toBeDefined();
    expect(featureRec?.actionSteps.length).toBeGreaterThan(0);
  });

  it('should prioritize recommendations by impact', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Result 1', snippet: 'Text 1' },
      { title: 'Result 2', snippet: 'Text 2' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    // Recommendations should be sorted by priority
    for (let i = 0; i < result.recommendations.length - 1; i++) {
      expect(result.recommendations[i].priority).toBeGreaterThanOrEqual(
        result.recommendations[i + 1].priority
      );
    }
  });
});

// ============================================================================
// P2 MEDIUM: ERROR HANDLING
// ============================================================================

describe('Error Handling', () => {
  beforeEach(() => {
    setupTestEnvironment();
    jest.clearAllMocks();
  });

  it('should handle network errors gracefully', async () => {
    // FIX: Mock rejection for all possible API calls (Google and SerpAPI)
    mockedAxios.get.mockRejectedValue(new Error('Network error'));

    const analyst = new SERPPatternAnalyst({
      keyword: 'test',
    });

    await expect(analyst.analyze()).rejects.toThrow(SERPAnalysisError);
  });

  it('should sanitize error messages', async () => {
    // FIX: Use existing environment setup, just mock the axios error
    mockedAxios.get.mockRejectedValueOnce(
      new Error('API key sk-1234567890abcdef is invalid')
    );

    const analyst = new SERPPatternAnalyst({
      keyword: 'test',
      serpApiKey: 'c8f9a3b2d1e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5',
    });

    try {
      await analyst.analyze();
    } catch (error) {
      expect(error).toBeInstanceOf(SERPAnalysisError);
      expect((error as Error).message).not.toContain('sk-1234567890abcdef');
      expect((error as Error).message).toContain('[REDACTED');
    }
  });

  it('should handle insufficient data errors', async () => {
    const mockResponse = createMockSerpAPIResponse([]);
    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });

    await expect(analyst.analyze()).rejects.toThrow('No search results returned');
  });

  it('should handle all API providers failing', async () => {
    mockedAxios.get
      .mockRejectedValueOnce(new Error('Google failed'))
      .mockRejectedValueOnce(new Error('SerpAPI failed'));

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });

    await expect(analyst.analyze()).rejects.toThrow('All API providers failed');
  });
});

// ============================================================================
// P2 MEDIUM: EDGE CASES
// ============================================================================

describe('Edge Cases', () => {
  beforeEach(() => {
    setupTestEnvironment();
    jest.clearAllMocks();
  });

  it('should handle minimal search results (5 results)', async () => {
    const mockResponse = createMockSerpAPIResponse(
      Array(5).fill(null).map((_, i) => ({
        title: `Result ${i + 1}`,
        link: `https://example${i}.com`,
        snippet: `Snippet ${i + 1}`,
      }))
    );

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test', maxResults: 5 });
    const result = await analyst.analyze();

    expect(result.results).toHaveLength(5);
    expect(result.confidence).toBeLessThan(0.8); // Lower confidence with fewer results
  });

  it('should handle results with missing data', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Result 1', snippet: '', link: 'https://example.com' },
      { title: '', snippet: 'Snippet 2', link: 'https://example2.com' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    expect(result.results).toHaveLength(2);
  });

  it('should handle very long URLs', async () => {
    const longUrl = 'https://example.com/' + 'a'.repeat(1000);
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Result', link: longUrl, snippet: 'Text' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    expect(result.results[0].url).toBe(longUrl);
    expect(result.rankingPatterns.urlStructure.avgUrlLength).toBeGreaterThan(100);
  });

  it('should handle special characters in URLs and titles', async () => {
    const mockResponse = createMockSerpAPIResponse([
      {
        title: 'Guide: How to Cook Pasta [2024]',
        link: 'https://example.com/guide?id=123&category=food',
        snippet: 'Learn how to cook perfect pasta every time!',
      },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'how to cook pasta' });
    const result = await analyst.analyze();

    expect(result.results[0].title).toContain('[2024]');
    expect(result.results[0].url).toContain('?');
  });

  it('should handle empty SERP results gracefully', async () => {
    const mockResponse = createMockSerpAPIResponse([]);  // Empty results

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({
      keyword: 'rare query',
      serpApiKey: 'c8f9a3b2d1e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5',
    });

    await expect(analyst.analyze()).rejects.toThrow('No search results returned');
  });

  it('should handle malformed API responses', async () => {
    mockedAxios.get.mockResolvedValueOnce({
      data: { invalid: 'response' },  // Missing organic_results
      status: 200,
      statusText: 'OK',
      headers: {},
      config: {},
    });

    const analyst = new SERPPatternAnalyst({
      keyword: 'test',
      serpApiKey: 'c8f9a3b2d1e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5',
    });

    await expect(analyst.analyze()).rejects.toThrow();
  });
});

// ============================================================================
// P2 MEDIUM: CONFIDENCE SCORING
// ============================================================================

describe('Confidence Scoring', () => {
  beforeEach(() => {
    setupTestEnvironment();
    jest.clearAllMocks();
  });

  it('should calculate higher confidence with more results', async () => {
    const mockResponse10 = createMockSerpAPIResponse(
      Array(10).fill(null).map((_, i) => ({ title: `Result ${i}` }))
    );

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse10 });

    const analyst10 = new SERPPatternAnalyst({ keyword: 'test' });
    const result10 = await analyst10.analyze();

    jest.clearAllMocks();

    const mockResponse5 = createMockSerpAPIResponse(
      Array(5).fill(null).map((_, i) => ({ title: `Result ${i}` }))
    );

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse5 });

    const analyst5 = new SERPPatternAnalyst({ keyword: 'test', maxResults: 5 });
    const result5 = await analyst5.analyze();

    expect(result10.confidence).toBeGreaterThan(result5.confidence);
  });

  it('should include confidence in overall result', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Result', snippet: 'Text' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    expect(result.confidence).toBeDefined();
    expect(result.confidence).toBeGreaterThanOrEqual(0);
    expect(result.confidence).toBeLessThanOrEqual(1);
  });
});

// ============================================================================
// P3 LOW: METADATA AND TIMING
// ============================================================================

describe('Metadata and Timing', () => {
  beforeEach(() => {
    setupTestEnvironment();
    jest.clearAllMocks();
  });

  it('should track analysis timing', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Result', snippet: 'Text' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    // FIX: Accept 0 or positive for mocked calls (instant response)
    expect(result.totalTimeMs).toBeGreaterThanOrEqual(0);
    expect(result.analyzedAt).toBeInstanceOf(Date);
  });

  it('should include metadata about API provider', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Result', snippet: 'Text' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({ keyword: 'test' });
    const result = await analyst.analyze();

    expect(result.metadata.apiProvider).toBeDefined();
    expect(['google', 'serpapi', 'scraping']).toContain(result.metadata.apiProvider);
  });

  it('should track warnings during analysis', async () => {
    const mockResponse = createMockSerpAPIResponse([
      { title: 'Result', snippet: 'Text' },
    ]);

    mockedAxios.get.mockResolvedValueOnce({ data: mockResponse });

    const analyst = new SERPPatternAnalyst({
      keyword: 'test',
      googleApiKey: '[REDACTED]',
      googleSearchEngineId: 'test',
      serpApiKey: 'valid-key-1234567890',
    });

    const result = await analyst.analyze();

    expect(result.warnings).toBeDefined();
    expect(Array.isArray(result.warnings)).toBe(true);
  });
});
