// @ts-nocheck
/**
 * Unit tests for lib/services/social-posting.service.ts
 * Tests social media posting functionality
 */

// Mock social-auth.service
jest.mock("@/lib/services/social-auth.service", () => ({
  getAccessToken: jest.fn().mockResolvedValue("mock-access-token"),
}));

// Mock Prisma - define inline to avoid hoisting issues
jest.mock("@/lib/db");

// Mock logger
jest.mock("@/lib/logger", () => ({
  logger: {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  },
}));

// Mock fetch
const mockFetch = jest.fn();
global.fetch = mockFetch;

import {
  SocialPostingService,
  socialPosting,
} from "@/lib/services/social-posting.service";
import { prisma } from "@/lib/db";

// Get mock references
const mockPrisma = prisma as jest.Mocked<typeof prisma>;

describe("SocialPostingService", () => {
  let service: SocialPostingService;

  beforeEach(() => {
    jest.clearAllMocks();
    service = new SocialPostingService();

    // Default mock for social account lookup
    mockPrisma.social_accounts.findUnique.mockResolvedValue({
      id: "account-123",
      platformAccountId: "page-123",
      metadata: { pageAccessToken: "page-token" },
    });
  });

  describe("postToFacebook", () => {
    const mockContent = {
      content: "Test post content",
      hashtags: ["test", "social"],
      mediaUrls: [],
      platform: "FACEBOOK" as const,
    };

    it("should post text content to Facebook", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "post-123" }),
      });

      const result = await service.postToFacebook("account-123", mockContent);

      expect(result.success).toBe(true);
      expect(result.platformPostId).toBe("post-123");
      expect(result.platformUrl).toContain("facebook.com");
    });

    it("should include hashtags in the post message", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "post-123" }),
      });

      await service.postToFacebook("account-123", mockContent);

      const fetchCall = mockFetch.mock.calls[0];
      const body = JSON.parse(fetchCall[1].body);
      expect(body.message).toContain("#test");
      expect(body.message).toContain("#social");
    });

    it("should post photo content to Facebook", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "photo-123" }),
      });

      const contentWithMedia = {
        ...mockContent,
        mediaUrls: ["https://example.com/image.jpg"],
      };

      const result = await service.postToFacebook(
        "account-123",
        contentWithMedia,
      );

      expect(result.success).toBe(true);
      const fetchCall = mockFetch.mock.calls[0];
      expect(fetchCall[0]).toContain("/photos");
    });

    it("should handle Facebook API errors", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 400,
        json: () =>
          Promise.resolve({ error: { message: "Invalid access token" } }),
      });

      const result = await service.postToFacebook("account-123", mockContent);

      expect(result.success).toBe(false);
      expect(result.error).toBe("Invalid access token");
      expect(result.retryable).toBe(false);
    });

    it("should mark server errors as retryable", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 500,
        json: () => Promise.resolve({ error: { message: "Server error" } }),
      });

      const result = await service.postToFacebook("account-123", mockContent);

      expect(result.success).toBe(false);
      expect(result.retryable).toBe(true);
    });

    it("should return error when account not found", async () => {
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce(null);

      const result = await service.postToFacebook("non-existent", mockContent);

      expect(result.success).toBe(false);
      expect(result.error).toBe("Account not found");
      expect(result.retryable).toBe(false);
    });

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

      const result = await service.postToFacebook("account-123", mockContent);

      expect(result.success).toBe(false);
      expect(result.error).toBe("Network error");
      expect(result.retryable).toBe(true);
    });
  });

  describe("postToInstagram", () => {
    const mockContent = {
      content: "Test Instagram post",
      hashtags: ["instagram", "test"],
      mediaUrls: ["https://example.com/image.jpg"],
      platform: "INSTAGRAM" as const,
    };

    it("should post image content to Instagram using two-step process", async () => {
      // Step 1: Create container
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "container-123" }),
      });
      // Step 2: Publish
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "ig-post-123" }),
      });

      const result = await service.postToInstagram("account-123", mockContent);

      expect(result.success).toBe(true);
      expect(result.platformPostId).toBe("ig-post-123");
      expect(mockFetch).toHaveBeenCalledTimes(2);
    });

    it("should require media for Instagram posts", async () => {
      const contentWithoutMedia = {
        ...mockContent,
        mediaUrls: [],
      };

      const result = await service.postToInstagram(
        "account-123",
        contentWithoutMedia,
      );

      expect(result.success).toBe(false);
      expect(result.error).toContain("requires at least one image or video");
      expect(result.retryable).toBe(false);
    });

    it("should handle container creation failure", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        json: () =>
          Promise.resolve({ error: { message: "Invalid image URL" } }),
      });

      const result = await service.postToInstagram("account-123", mockContent);

      expect(result.success).toBe(false);
      expect(result.error).toContain("Invalid image URL");
    });

    it("should handle publish failure after container creation", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "container-123" }),
      });
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 500,
        json: () => Promise.resolve({ error: { message: "Publish failed" } }),
      });

      const result = await service.postToInstagram("account-123", mockContent);

      expect(result.success).toBe(false);
      expect(result.retryable).toBe(true);
    });

    it("should return error when account not found", async () => {
      mockPrisma.social_accounts.findUnique.mockResolvedValueOnce(null);

      const result = await service.postToInstagram("non-existent", mockContent);

      expect(result.success).toBe(false);
      expect(result.error).toBe("Account not found");
    });
  });

  describe("publishQueueItem", () => {
    const mockQueueItem = {
      id: "queue-123",
      platform: "FACEBOOK",
      content: "Queued post content",
      hashtags: ["queued"],
      mediaUrls: [],
      status: "SCHEDULED",
      socialAccountId: "account-123",
      retryCount: 0,
      social_account: {
        id: "account-123",
        platformAccountId: "page-123",
      },
    };

    beforeEach(() => {
      mockPrisma.social_content_queue.findUnique.mockResolvedValue(
        mockQueueItem,
      );
      mockPrisma.social_content_queue.update.mockResolvedValue({});
    });

    it("should publish queue item and update status to PUBLISHED", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "post-123" }),
      });

      const result = await service.publishQueueItem("queue-123");

      expect(result.success).toBe(true);
      expect(mockPrisma.social_content_queue.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: "queue-123" },
          data: expect.objectContaining({
            status: "PUBLISHED",
            platformPostId: "post-123",
          }),
        }),
      );
    });

    it("should update status to PUBLISHING before posting", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "post-123" }),
      });

      await service.publishQueueItem("queue-123");

      // First update should set status to PUBLISHING
      expect(
        mockPrisma.social_content_queue.update.mock.calls[0][0].data.status,
      ).toBe("PUBLISHING");
    });

    it("should update status to FAILED on error", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 400,
        json: () => Promise.resolve({ error: { message: "API error" } }),
      });

      await service.publishQueueItem("queue-123");

      const lastUpdateCall =
        mockPrisma.social_content_queue.update.mock.calls.pop();
      expect(lastUpdateCall[0].data.status).toBe("FAILED");
      expect(lastUpdateCall[0].data.errorMessage).toBe("API error");
    });

    it("should increment retry count on failure", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 500,
        json: () => Promise.resolve({ error: { message: "Server error" } }),
      });

      await service.publishQueueItem("queue-123");

      const lastUpdateCall =
        mockPrisma.social_content_queue.update.mock.calls.pop();
      expect(lastUpdateCall[0].data.retryCount).toBe(1);
    });

    it("should return error for non-existent queue item", async () => {
      mockPrisma.social_content_queue.findUnique.mockResolvedValueOnce(null);

      const result = await service.publishQueueItem("non-existent");

      expect(result.success).toBe(false);
      expect(result.error).toBe("Queue item not found");
    });

    it("should return error for already published item", async () => {
      mockPrisma.social_content_queue.findUnique.mockResolvedValueOnce({
        ...mockQueueItem,
        status: "PUBLISHED",
      });

      const result = await service.publishQueueItem("queue-123");

      expect(result.success).toBe(false);
      expect(result.error).toBe("Already published");
    });

    it("should handle Instagram queue items", async () => {
      mockPrisma.social_content_queue.findUnique.mockResolvedValueOnce({
        ...mockQueueItem,
        platform: "INSTAGRAM",
        mediaUrls: ["https://example.com/image.jpg"],
      });

      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve({ id: "container-123" }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve({ id: "ig-post-123" }),
        });

      const result = await service.publishQueueItem("queue-123");

      expect(result.success).toBe(true);
    });

    it("should return error for unsupported platforms", async () => {
      mockPrisma.social_content_queue.findUnique.mockResolvedValueOnce({
        ...mockQueueItem,
        platform: "TWITTER",
      });

      const result = await service.publishQueueItem("queue-123");

      expect(result.success).toBe(false);
      expect(result.error).toContain("coming soon");
    });
  });

  describe("retryFailedPost", () => {
    it("should retry failed queue item", async () => {
      mockPrisma.social_content_queue.findUnique.mockResolvedValueOnce({
        id: "queue-123",
        platform: "FACEBOOK",
        content: "Failed post",
        hashtags: [],
        mediaUrls: [],
        status: "FAILED",
        socialAccountId: "account-123",
        retryCount: 1,
      });

      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "post-123" }),
      });

      const result = await service.retryFailedPost("queue-123");

      expect(result.success).toBe(true);
    });

    it("should reject retry for non-failed items", async () => {
      mockPrisma.social_content_queue.findUnique.mockResolvedValueOnce({
        id: "queue-123",
        status: "SCHEDULED",
        retryCount: 0,
      });

      const result = await service.retryFailedPost("queue-123");

      expect(result.success).toBe(false);
      expect(result.error).toContain("not in failed state");
    });

    it("should reject retry when max attempts exceeded", async () => {
      mockPrisma.social_content_queue.findUnique.mockResolvedValueOnce({
        id: "queue-123",
        status: "FAILED",
        retryCount: 3, // MAX_RETRIES
      });

      const result = await service.retryFailedPost("queue-123");

      expect(result.success).toBe(false);
      expect(result.error).toContain("Maximum retry attempts exceeded");
    });

    it("should return error for non-existent queue item", async () => {
      mockPrisma.social_content_queue.findUnique.mockResolvedValueOnce(null);

      const result = await service.retryFailedPost("non-existent");

      expect(result.success).toBe(false);
      expect(result.error).toBe("Queue item not found");
    });
  });

  describe("getScheduledItemsToPublish", () => {
    it("should return scheduled items that are due", async () => {
      const pastDate = new Date(Date.now() - 60000);
      const mockItems = [
        { id: "item-1", status: "SCHEDULED", scheduledAt: pastDate },
        { id: "item-2", status: "SCHEDULED", scheduledAt: pastDate },
      ];

      mockPrisma.social_content_queue.findMany.mockResolvedValueOnce(mockItems);

      const items = await service.getScheduledItemsToPublish();

      expect(items).toHaveLength(2);
      expect(mockPrisma.social_content_queue.findMany).toHaveBeenCalledWith({
        where: {
          status: "SCHEDULED",
          scheduledAt: { lte: expect.any(Date) },
        },
        orderBy: { scheduledAt: "asc" },
      });
    });

    it("should return empty array when no items are due", async () => {
      mockPrisma.social_content_queue.findMany.mockResolvedValueOnce([]);

      const items = await service.getScheduledItemsToPublish();

      expect(items).toEqual([]);
    });
  });

  describe("processScheduledPosts", () => {
    it("should process multiple scheduled items", async () => {
      const mockItems = [
        {
          id: "item-1",
          platform: "FACEBOOK",
          content: "Post 1",
          hashtags: [],
          mediaUrls: [],
          status: "SCHEDULED",
          socialAccountId: "account-123",
          retryCount: 0,
        },
        {
          id: "item-2",
          platform: "FACEBOOK",
          content: "Post 2",
          hashtags: [],
          mediaUrls: [],
          status: "SCHEDULED",
          socialAccountId: "account-123",
          retryCount: 0,
        },
      ];

      mockPrisma.social_content_queue.findMany.mockResolvedValueOnce(mockItems);
      mockPrisma.social_content_queue.findUnique
        .mockResolvedValueOnce(mockItems[0])
        .mockResolvedValueOnce(mockItems[1]);

      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve({ id: "post-1" }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve({ id: "post-2" }),
        });

      const result = await service.processScheduledPosts();

      expect(result.processed).toBe(2);
      expect(result.success).toBe(2);
      expect(result.failed).toBe(0);
    });

    it("should track failed items separately", async () => {
      const mockItems = [
        {
          id: "item-1",
          platform: "FACEBOOK",
          content: "Post 1",
          hashtags: [],
          mediaUrls: [],
          status: "SCHEDULED",
          socialAccountId: "account-123",
          retryCount: 0,
        },
        {
          id: "item-2",
          platform: "FACEBOOK",
          content: "Post 2",
          hashtags: [],
          mediaUrls: [],
          status: "SCHEDULED",
          socialAccountId: "account-123",
          retryCount: 0,
        },
      ];

      mockPrisma.social_content_queue.findMany.mockResolvedValueOnce(mockItems);
      mockPrisma.social_content_queue.findUnique
        .mockResolvedValueOnce(mockItems[0])
        .mockResolvedValueOnce(mockItems[1]);

      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: () => Promise.resolve({ id: "post-1" }),
        })
        .mockResolvedValueOnce({
          ok: false,
          status: 400,
          json: () => Promise.resolve({ error: { message: "Error" } }),
        });

      const result = await service.processScheduledPosts();

      expect(result.processed).toBe(2);
      expect(result.success).toBe(1);
      expect(result.failed).toBe(1);
    });

    it("should return zeros when no items to process", async () => {
      mockPrisma.social_content_queue.findMany.mockResolvedValueOnce([]);

      const result = await service.processScheduledPosts();

      expect(result.processed).toBe(0);
      expect(result.success).toBe(0);
      expect(result.failed).toBe(0);
    });
  });

  describe("singleton export", () => {
    it("should export a singleton instance", () => {
      expect(socialPosting).toBeInstanceOf(SocialPostingService);
    });
  });

  describe("message building", () => {
    it("should properly format hashtags without existing # prefix", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "post-123" }),
      });

      const content = {
        content: "Test content",
        hashtags: ["test", "social", "media"],
        mediaUrls: [],
        platform: "FACEBOOK" as const,
      };

      await service.postToFacebook("account-123", content);

      const fetchCall = mockFetch.mock.calls[0];
      const body = JSON.parse(fetchCall[1].body);
      expect(body.message).toContain("#test #social #media");
    });

    it("should not duplicate # for hashtags that already have it", async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ id: "post-123" }),
      });

      const content = {
        content: "Test content",
        hashtags: ["#already", "without"],
        mediaUrls: [],
        platform: "FACEBOOK" as const,
      };

      await service.postToFacebook("account-123", content);

      const fetchCall = mockFetch.mock.calls[0];
      const body = JSON.parse(fetchCall[1].body);
      // #already should stay as #already, without should become #without
      expect(body.message).toContain("#already");
      expect(body.message).toContain("#without");
    });
  });
});
