/**
 * Tests for SEOWordPressClient
 *
 * Comprehensive test coverage for WordPress SEO integration client,
 * including plugin detection, metadata extraction, and bulk operations.
 *
 * @since 2.7.0
 */

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { SEOWordPressClient } from "../../dist/client/SEOWordPressClient.js";
// import { LoggerFactory } from "../../dist/utils/logger.js";

// Mock the logger to avoid console output during tests
vi.mock("../../dist/utils/logger.js", () => {
  const createMockLogger = () => ({
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
    time: vi.fn().mockImplementation((_name, fn) => fn()),
    child: vi.fn().mockReturnThis(),
  });
  return {
    LoggerFactory: {
      api: createMockLogger,
      client: createMockLogger,
      tool: createMockLogger,
      server: createMockLogger,
      cache: createMockLogger,
      security: createMockLogger,
    },
    startTimer: () => ({ end: () => 0 }),
  };
});
// Also mock error handling utilities
vi.mock("../../dist/utils/error.js", () => ({
  handleToolError: vi.fn(),
  validateRequired: vi.fn(),
  validateSite: vi.fn(),
  getErrorMessage: vi.fn().mockReturnValue("Mocked error"),
}));

describe("SEOWordPressClient", () => {
  let client;
  let mockWordPressClient;

  beforeEach(() => {
    // Mock the WordPressClient base class
    mockWordPressClient = {
      get: vi.fn(),
      post: vi.fn(),
      put: vi.fn(),
      delete: vi.fn(),
      getPost: vi.fn(),
      getPage: vi.fn(),
      updatePost: vi.fn(),
      updatePage: vi.fn(),
    };

    // Create SEOWordPressClient with mocked base
    client = new SEOWordPressClient({
      baseUrl: "https://test.example.com",
      auth: {
        method: "app-password",
        username: "testuser",
        appPassword: "test password",
      },
    });

    // Replace the base client methods with mocks
    Object.assign(client, mockWordPressClient);

    // Reset console mocks
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe("Plugin Detection", () => {
    it("should detect Yoast SEO plugin", async () => {
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "wordpress-seo", status: "active" }]);

      await client.initializeSEO();

      expect(client.detectedPlugin).toBe("yoast");
      expect(mockWordPressClient.get).toHaveBeenCalledWith("/wp/v2/plugins");
    });

    it("should detect RankMath SEO plugin", async () => {
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "seo-by-rank-math", status: "active" }]);

      await client.initializeSEO();

      expect(client.detectedPlugin).toBe("rankmath");
    });

    it("should detect SEOPress plugin", async () => {
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "wp-seopress", status: "active" }]);

      await client.initializeSEO();

      expect(client.detectedPlugin).toBe("seopress");
    });

    it("should handle no SEO plugin detected", async () => {
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "some-other-plugin", status: "active" }]);

      await client.initializeSEO();

      expect(client.detectedPlugin).toBe("none");
    });

    it("should handle plugin detection API errors", async () => {
      mockWordPressClient.get.mockRejectedValueOnce(new Error("API Error"));

      await client.initializeSEO();

      expect(client.detectedPlugin).toBe("none");
    });

    it("should prefer Yoast when multiple SEO plugins are active", async () => {
      mockWordPressClient.get.mockResolvedValueOnce([
        { slug: "wordpress-seo", status: "active" },
        { slug: "seo-by-rank-math", status: "active" },
        { slug: "wp-seopress", status: "active" },
      ]);

      await client.initializeSEO();

      expect(client.detectedPlugin).toBe("yoast");
    });

    it("should handle inactive SEO plugins", async () => {
      mockWordPressClient.get.mockResolvedValueOnce([
        { slug: "wordpress-seo", status: "inactive" },
        { slug: "seo-by-rank-math", status: "active" },
      ]);

      await client.initializeSEO();

      expect(client.detectedPlugin).toBe("rankmath");
    });
  });

  describe("SEO Metadata Extraction", () => {
    beforeEach(async () => {
      // Set up with Yoast detected
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "wordpress-seo", status: "active" }]);
      await client.initializeSEO();
    });

    it("should extract Yoast SEO metadata", async () => {
      const mockPost = {
        id: 123,
        title: { rendered: "Test Post" },
        content: { rendered: "Test content" },
        type: "post",
        link: "https://example.com/test-post",
        meta: {
          yoast_head_json: {
            title: "Optimized Title",
            description: "Optimized description",
            canonical: "https://example.com/test-post",
            og_title: "OpenGraph Title",
            og_description: "OpenGraph Description",
            twitter_title: "Twitter Title",
            twitter_description: "Twitter Description",
          },
          _yoast_wpseo_title: "Optimized Title",
          _yoast_wpseo_metadesc: "Optimized description",
          _yoast_wpseo_canonical: "https://example.com/test-post",
        },
      };

      mockWordPressClient.getPost.mockResolvedValueOnce(mockPost);

      const result = await client.getSEOMetadata(123);

      expect(result).toMatchObject({
        postId: 123,
        plugin: "yoast",
        title: "Optimized Title",
        description: "Optimized description",
        canonical: "https://example.com/test-post",
        openGraph: {
          title: "OpenGraph Title",
          description: "OpenGraph Description",
          type: "article",
        },
        twitter: {
          title: "Twitter Title",
          description: "Twitter Description",
        },
      });
      expect(mockWordPressClient.getPost).toHaveBeenCalledWith(123, "edit");
    });

    it("should handle post without SEO metadata", async () => {
      const mockPost = {
        id: 123,
        title: { rendered: "Test Post" },
        content: { rendered: "Test content" },
        type: "post",
        link: "https://example.com/test-post",
        meta: {},
      };

      mockWordPressClient.getPost.mockResolvedValueOnce(mockPost);

      const result = await client.getSEOMetadata(123);

      expect(result).toMatchObject({
        postId: 123,
        plugin: "yoast",
        title: null, // Plugin detected but no data = null
        description: null,
        canonical: null,
        focusKeyword: null,
        openGraph: {
          title: "Test Post", // Falls back to post title
          description: "",
          type: "article",
        },
      });
    });

    it("should handle RankMath metadata", async () => {
      // Reset with RankMath detected
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "seo-by-rank-math", status: "active" }]);
      await client.initializeSEO();

      const mockPost = {
        id: 123,
        title: { rendered: "Test Post" },
        content: { rendered: "Test content" },
        meta: {
          rank_math_title: "RankMath Title",
          rank_math_description: "RankMath Description",
          rank_math_focus_keyword: "test keyword",
          rank_math_canonical_url: "https://example.com/canonical",
        },
      };

      mockWordPressClient.getPost.mockResolvedValueOnce(mockPost);

      const result = await client.getSEOMetadata(123);

      expect(result.title).toBe("RankMath Title");
      expect(result.description).toBe("RankMath Description");
      expect(result.focusKeyword).toBe("test keyword");
      expect(result.canonical).toBe("https://example.com/canonical");
      expect(result.plugin).toBe("rankmath");
    });

    it("should handle SEOPress metadata", async () => {
      // Reset with SEOPress detected
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "wp-seopress", status: "active" }]);
      await client.initializeSEO();

      const mockPost = {
        id: 123,
        title: { rendered: "Test Post" },
        content: { rendered: "Test content" },
        meta: {
          _seopress_titles_title: "SEOPress Title",
          _seopress_titles_desc: "SEOPress Description",
          _seopress_analysis_target_kw: "seopress keyword",
        },
      };

      mockWordPressClient.getPost.mockResolvedValueOnce(mockPost);

      const result = await client.getSEOMetadata(123);

      expect(result.title).toBe("SEOPress Title");
      expect(result.description).toBe("SEOPress Description");
      expect(result.focusKeyword).toBe("seopress keyword");
      expect(result.plugin).toBe("seopress");
    });

    it("should handle API errors gracefully", async () => {
      mockWordPressClient.getPost.mockRejectedValueOnce(new Error("post with ID 123 not found"));

      await expect(client.getSEOMetadata(123)).rejects.toThrow("post with ID 123 not found");
    });

    it("should handle pages correctly", async () => {
      const mockPage = {
        id: 456,
        title: { rendered: "Test Page" },
        content: { rendered: "Test page content" },
        type: "page",
        link: "https://example.com/test-page",
        meta: {
          yoast_head_json: {
            title: "Page Title",
            description: "Page description",
          },
          _yoast_wpseo_title: "Page Title",
          _yoast_wpseo_metadesc: "Page description",
        },
      };

      mockWordPressClient.getPage.mockResolvedValueOnce(mockPage);

      const result = await client.getSEOMetadata(456, "page");

      expect(mockWordPressClient.getPage).toHaveBeenCalledWith(456, "edit");
      expect(result.title).toBe("Page Title");
    });
  });

  describe("Bulk SEO Operations", () => {
    beforeEach(async () => {
      // Set up with Yoast detected
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "wordpress-seo", status: "active" }]);
      await client.initializeSEO();
    });

    it("should process bulk metadata requests", async () => {
      const mockPosts = [
        {
          id: 1,
          title: { rendered: "Post 1" },
          type: "post",
          link: "https://example.com/post-1",
          meta: {
            yoast_head_json: { title: "Post 1", description: "Desc 1" },
            _yoast_wpseo_title: "Post 1",
            _yoast_wpseo_metadesc: "Desc 1",
          },
        },
        {
          id: 2,
          title: { rendered: "Post 2" },
          type: "post",
          link: "https://example.com/post-2",
          meta: {
            yoast_head_json: { title: "Post 2", description: "Desc 2" },
            _yoast_wpseo_title: "Post 2",
            _yoast_wpseo_metadesc: "Desc 2",
          },
        },
        {
          id: 3,
          title: { rendered: "Post 3" },
          type: "post",
          link: "https://example.com/post-3",
          meta: {
            yoast_head_json: { title: "Post 3", description: "Desc 3" },
            _yoast_wpseo_title: "Post 3",
            _yoast_wpseo_metadesc: "Desc 3",
          },
        },
      ];

      mockWordPressClient.getPost
        .mockResolvedValueOnce(mockPosts[0])
        .mockResolvedValueOnce(mockPosts[1])
        .mockResolvedValueOnce(mockPosts[2]);

      const result = await client.bulkGetSEOMetadata({
        postIds: [1, 2, 3],
        batchSize: 2,
      });

      expect(result).toHaveLength(3);
      expect(result[0].title).toBe("Post 1");
      expect(result[1].title).toBe("Post 2");
      expect(result[2].title).toBe("Post 3");
      expect(mockWordPressClient.getPost).toHaveBeenCalledTimes(3); // 3 individual post calls
    });

    it("should handle batch size correctly", async () => {
      const postIds = [1, 2, 3, 4, 5];
      const mockResponses = postIds.map((id) => ({
        id,
        yoast_head_json: { title: `Post ${id}` },
      }));

      // Mock each individual post request
      mockResponses.forEach((post) => {
        mockWordPressClient.getPost.mockResolvedValueOnce(post);
      });

      const result = await client.bulkGetSEOMetadata({
        postIds,
        batchSize: 2,
      });

      expect(result).toHaveLength(5);
      expect(mockWordPressClient.getPost).toHaveBeenCalledTimes(5); // 5 individual post calls
    });

    it("should handle individual post failures in bulk operations", async () => {
      mockWordPressClient.getPost
        .mockResolvedValueOnce({ id: 1, yoast_head_json: { title: "Post 1" } })
        .mockRejectedValueOnce(new Error("Post 2 not found"))
        .mockResolvedValueOnce({ id: 3, yoast_head_json: { title: "Post 3" } });

      const result = await client.bulkGetSEOMetadata({
        postIds: [1, 2, 3],
        continueOnError: true,
      });

      // Should return results for successful posts only
      expect(result).toHaveLength(2);
      expect(result[0].postId).toBe(1);
      expect(result[1].postId).toBe(3);
    });

    it("should respect continueOnError setting", async () => {
      mockWordPressClient.getPost
        .mockResolvedValueOnce({ id: 1, yoast_head_json: { title: "Post 1" } })
        .mockRejectedValueOnce(new Error("Post 2 not found"));

      // Current implementation always continues on error, returning successful results
      const result = await client.bulkGetSEOMetadata({
        postIds: [1, 2, 3],
      });

      // Should return only successful posts (post 1), skipping failed ones
      expect(result).toHaveLength(1);
      expect(result[0].postId).toBe(1);
    });

    it("should handle empty post IDs array", async () => {
      const result = await client.bulkGetSEOMetadata({
        postIds: [],
      });

      expect(result).toEqual([]);
      expect(mockWordPressClient.get).toHaveBeenCalledTimes(1); // Only plugin detection
    });

    it("should handle mixed post types in bulk operations", async () => {
      mockWordPressClient.getPost
        .mockResolvedValueOnce({ id: 1, yoast_head_json: { title: "Post 1" } })
        .mockResolvedValueOnce({ id: 2, yoast_head_json: { title: "Page 2" } });

      const result = await client.bulkGetSEOMetadata({
        postIds: [1, 2],
      });

      expect(result).toHaveLength(2);
      expect(mockWordPressClient.getPost).toHaveBeenCalledTimes(2);
    });
  });

  describe("SEO Metadata Updates", () => {
    beforeEach(async () => {
      // Set up with Yoast detected
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "wordpress-seo", status: "active" }]);
      await client.initializeSEO();
    });

    it("should update Yoast SEO metadata", async () => {
      const updates = {
        title: "New Title",
        description: "New Description",
        canonical: "https://example.com/new-canonical",
        focusKeyword: "new keyword",
      };

      // Mock the update call
      mockWordPressClient.updatePost.mockResolvedValueOnce({
        id: 123,
        meta: {
          _yoast_wpseo_title: "New Title",
          _yoast_wpseo_metadesc: "New Description",
        },
      });

      // Mock the subsequent getSEOMetadata call
      const mockUpdatedPost = {
        id: 123,
        title: { rendered: "Test Post" },
        type: "post",
        link: "https://example.com/test-post",
        meta: {
          yoast_head_json: {
            title: "New Title",
            description: "New Description",
          },
          _yoast_wpseo_title: "New Title",
          _yoast_wpseo_metadesc: "New Description",
          _yoast_wpseo_canonical: "https://example.com/new-canonical",
          _yoast_wpseo_focuskw: "new keyword",
        },
      };

      mockWordPressClient.getPost.mockResolvedValueOnce(mockUpdatedPost);

      const result = await client.updateSEOMetadata(123, updates);

      expect(result.title).toBe("New Title");
      expect(result.description).toBe("New Description");
      expect(mockWordPressClient.updatePost).toHaveBeenCalledWith(
        expect.objectContaining({
          id: 123,
          meta: expect.objectContaining({
            _yoast_wpseo_title: "New Title",
            _yoast_wpseo_metadesc: "New Description",
            _yoast_wpseo_canonical: "https://example.com/new-canonical",
            _yoast_wpseo_focuskw: "new keyword",
          }),
        }),
      );
    });

    it("should update RankMath metadata", async () => {
      // Reset with RankMath detected
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "seo-by-rank-math", status: "active" }]);
      await client.initializeSEO();

      const updates = {
        title: "RankMath Title",
        description: "RankMath Description",
      };

      // Mock updatePost call
      mockWordPressClient.updatePost.mockResolvedValueOnce({ success: true });

      // Mock the final getSEOMetadata call - this calls getPost
      mockWordPressClient.getPost.mockResolvedValueOnce({
        id: 123,
        title: { rendered: "Test Post" },
        meta: {
          rank_math_title: "RankMath Title",
          rank_math_description: "RankMath Description",
        },
      });

      await client.updateSEOMetadata(123, updates);

      expect(mockWordPressClient.updatePost).toHaveBeenCalledWith(
        expect.objectContaining({
          id: 123,
          meta: expect.objectContaining({
            rank_math_title: "RankMath Title",
            rank_math_description: "RankMath Description",
          }),
        }),
      );
    });

    it("should update SEOPress metadata", async () => {
      // Reset with SEOPress detected
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "wp-seopress", status: "active" }]);
      await client.initializeSEO();

      const updates = {
        title: "SEOPress Title",
        description: "SEOPress Description",
      };

      // Mock updatePost call
      mockWordPressClient.updatePost.mockResolvedValueOnce({ success: true });

      // Mock the final getSEOMetadata call - this calls getPost
      mockWordPressClient.getPost.mockResolvedValueOnce({
        id: 123,
        title: { rendered: "Test Post" },
        meta: {
          _seopress_titles_title: "SEOPress Title",
          _seopress_titles_desc: "SEOPress Description",
        },
      });

      await client.updateSEOMetadata(123, updates);

      expect(mockWordPressClient.updatePost).toHaveBeenCalledWith(
        expect.objectContaining({
          id: 123,
          meta: expect.objectContaining({
            _seopress_titles_title: "SEOPress Title",
            _seopress_titles_desc: "SEOPress Description",
          }),
        }),
      );
    });

    it("should handle no plugin detected gracefully", async () => {
      // Create a fresh client to avoid state pollution
      const freshClient = new SEOWordPressClient({
        baseUrl: "https://test.example.com",
        auth: {
          method: "app-password",
          username: "testuser",
          appPassword: "test password",
        },
      });

      // Apply all the mocked methods to the fresh client
      Object.assign(freshClient, mockWordPressClient);

      // Mock no SEO plugins detected
      mockWordPressClient.get.mockResolvedValueOnce([]);
      await freshClient.initializeSEO();

      const updates = { title: "New Title" };

      // Mock getPost for the getSEOMetadata call
      mockWordPressClient.getPost.mockResolvedValueOnce({
        id: 123,
        title: { rendered: "Test Post" },
        meta: {},
      });

      const result = await freshClient.updateSEOMetadata(123, updates);

      expect(result.success).toBe(false);
      expect(result.message).toContain("No SEO plugin detected");
      expect(mockWordPressClient.updatePost).not.toHaveBeenCalled();
    });

    it("should handle API errors during updates", async () => {
      const updates = { title: "New Title" };

      // Mock updatePost to fail
      mockWordPressClient.updatePost.mockRejectedValueOnce(new Error("Update failed"));

      await expect(client.updateSEOMetadata(123, updates)).rejects.toThrow("Update failed");
    });

    it("should handle partial updates", async () => {
      const updates = {
        title: "Only Title Updated",
      };

      // Mock updatePost call
      mockWordPressClient.updatePost.mockResolvedValueOnce({ success: true });

      // Mock the final getSEOMetadata call - this calls getPost
      mockWordPressClient.getPost.mockResolvedValueOnce({
        id: 123,
        title: { rendered: "Test Post" },
        meta: {
          _yoast_wpseo_title: "Only Title Updated",
        },
      });

      await client.updateSEOMetadata(123, updates);

      expect(mockWordPressClient.updatePost).toHaveBeenCalledWith(
        expect.objectContaining({
          id: 123,
          meta: expect.objectContaining({
            _yoast_wpseo_title: "Only Title Updated",
          }),
        }),
      );

      // Should not include other fields
      const callArgs = mockWordPressClient.updatePost.mock.calls[0][0];
      expect(callArgs.meta._yoast_wpseo_metadesc).toBeUndefined();
      expect(callArgs.meta._yoast_wpseo_canonical).toBeUndefined();
    });

    it("should update pages correctly", async () => {
      const updates = { title: "Page Title" };

      // Mock updatePage call
      mockWordPressClient.updatePage.mockResolvedValueOnce({ success: true });

      // Mock the final getSEOMetadata call - this calls getPage
      mockWordPressClient.getPage.mockResolvedValueOnce({
        id: 456,
        title: { rendered: "Test Page" },
        meta: {
          _yoast_wpseo_title: "Page Title",
        },
      });

      await client.updateSEOMetadata(456, updates, "page");

      expect(mockWordPressClient.updatePage).toHaveBeenCalledWith(
        expect.objectContaining({
          id: 456,
          meta: expect.objectContaining({
            _yoast_wpseo_title: "Page Title",
          }),
        }),
      );
    });
  });

  describe("Error Handling", () => {
    it("should handle network errors", async () => {
      mockWordPressClient.get.mockRejectedValueOnce(new Error("Network error"));

      await expect(client.initializeSEO()).resolves.not.toThrow();
      expect(client.detectedPlugin).toBe("none");
    });

    it("should handle malformed plugin data", async () => {
      mockWordPressClient.get.mockResolvedValueOnce([
        {
          /* missing slug and status */
        },
        null,
        undefined,
      ]);

      await client.initializeSEO();

      expect(client.detectedPlugin).toBe("none");
    });

    it("should handle malformed SEO metadata", async () => {
      const mockPost = {
        id: 123,
        title: { rendered: "Test Post" },
        yoast_head_json: null,
      };

      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "wordpress-seo", status: "active" }]);

      mockWordPressClient.getPost.mockResolvedValueOnce(mockPost);

      await client.initializeSEO();
      const result = await client.getSEOMetadata(123);

      expect(result.title).toBe(null);
      expect(result.raw).toEqual({});
    });
  });

  describe("Integration Status", () => {
    beforeEach(async () => {
      mockWordPressClient.get.mockResolvedValueOnce([{ slug: "wordpress-seo", status: "active" }]);
      await client.initializeSEO();
    });

    it("should report integration status", () => {
      const status = client.getIntegrationStatus();

      expect(status).toEqual({
        hasPlugin: true,
        plugin: "yoast",
        canReadMetadata: true,
        canWriteMetadata: true,
        features: {
          metaTags: true,
          schema: true,
          socialMedia: true,
          xmlSitemap: false, // Would need additional API calls
          breadcrumbs: false,
        },
      });
    });

    it("should report no plugin status", async () => {
      // Reset with no plugin
      const noPluginClient = new SEOWordPressClient({
        baseUrl: "https://test.example.com",
        auth: {
          method: "app-password",
          username: "testuser",
          appPassword: "test password",
        },
      });
      Object.assign(noPluginClient, mockWordPressClient);

      mockWordPressClient.get.mockResolvedValueOnce([]);
      await noPluginClient.initializeSEO();

      const status = noPluginClient.getIntegrationStatus();

      expect(status).toEqual({
        hasPlugin: false,
        plugin: "none",
        canReadMetadata: false,
        canWriteMetadata: false,
        features: {
          metaTags: false,
          schema: false,
          socialMedia: false,
          xmlSitemap: false,
          breadcrumbs: false,
        },
      });
    });
  });
});
