import {
  DeleteObjectsCommand,
  ListObjectsV2Command,
  PutObjectCommand,
  S3Client,
} from "@aws-sdk/client-s3";
import { mockClient } from "aws-sdk-client-mock";
import { testClient } from "hono/testing";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createConfig } from "../../config.js";
import { MiddlewareManager } from "../../middlewares/middleware.js";
import { getMainRouter } from "../../routers/index.js";
import { getTestUser } from "../helpers/dbSetup.js";

// Mock Stream Chat operations
const mockUpsertUser = vi.fn();
const mockCreateToken = vi.fn();

// Create an instance object that holds the mocks (like the working pattern in tablo.test.ts)
const mockStreamChatInstanceMethods = {
  upsertUser: mockUpsertUser,
  createToken: mockCreateToken,
};

// Mock the stream-chat module
vi.mock("stream-chat", () => {
  return {
    StreamChat: {
      getInstance: vi.fn(() => mockStreamChatInstanceMethods),
    },
  };
});

// Create S3 mock for avatar operations
const s3Mock = mockClient(S3Client);

describe("User Endpoint", () => {
  // In test mode, createConfig() reads from .env.test
  const config = createConfig();
  MiddlewareManager.initialize(config);
  const app = getMainRouter(config);
  // biome-ignore lint/suspicious/noExplicitAny: testClient requires any for dynamic route access
  const client = testClient(app) as any;

  const ownerUser = getTestUser("owner");
  const temporaryUser = getTestUser("temp");

  beforeEach(() => {
    // Reset all mocks before each test
    vi.clearAllMocks();
    s3Mock.reset();

    // Mock Stream Chat operations
    mockUpsertUser.mockResolvedValue({ users: { [ownerUser.userId]: {} } });
    mockCreateToken.mockReturnValue("mock-stream-token-123");

    // Mock S3 operations
    s3Mock.on(PutObjectCommand).resolves({});
    s3Mock.on(ListObjectsV2Command).resolves({
      Contents: [{ Key: `${ownerUser.userId}/public_avatar_test.jpg` }],
    });
    s3Mock.on(DeleteObjectsCommand).resolves({});
  });

  describe("GET /me - Get User Profile", () => {
    it("should return owner user profile with stream token", async () => {
      const res = await client.users.me.$get(
        {},
        {
          headers: {
            Authorization: `Bearer ${ownerUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.id).toBe(ownerUser.userId);
      expect(data.email).toBe(ownerUser.email);
      expect(data.streamToken).toBe("mock-stream-token-123");

      // Verify Stream Chat createToken was called
      expect(mockCreateToken).toHaveBeenCalledTimes(1);
      expect(mockCreateToken).toHaveBeenCalledWith(ownerUser.userId);
    });

    it("should return temp user profile with stream token", async () => {
      const res = await client.users.me.$get(
        {},
        {
          headers: {
            Authorization: `Bearer ${temporaryUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.id).toBe(temporaryUser.userId);
      expect(data.email).toBe(temporaryUser.email);
      expect(data.streamToken).toBe("mock-stream-token-123");

      // Verify Stream Chat createToken was called
      expect(mockCreateToken).toHaveBeenCalledTimes(1);
      expect(mockCreateToken).toHaveBeenCalledWith(temporaryUser.userId);
    });

    it("should deny unauthenticated access", async () => {
      const res = await client.users.me.$get({});

      expect(res.status).toBe(401);
    });
  });

  describe("POST /sign-up-to-stream - Sign Up User to Stream Chat", () => {
    it("should sign up owner user to stream chat", async () => {
      const res = await client.users["sign-up-to-stream"].$post(
        {},
        {
          headers: {
            Authorization: `Bearer ${ownerUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.message).toBe("User signed up to stream");

      // Verify Stream Chat upsertUser was called
      expect(mockUpsertUser).toHaveBeenCalledTimes(1);
      expect(mockUpsertUser).toHaveBeenCalledWith({
        id: ownerUser.userId,
        name: expect.any(String),
        language: "fr",
      });
    });

    it("should sign up temp user to stream chat", async () => {
      const res = await client.users["sign-up-to-stream"].$post(
        {},
        {
          headers: {
            Authorization: `Bearer ${temporaryUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.message).toBe("User signed up to stream");

      // Verify Stream Chat upsertUser was called
      expect(mockUpsertUser).toHaveBeenCalledTimes(1);
      expect(mockUpsertUser).toHaveBeenCalledWith({
        id: temporaryUser.userId,
        name: expect.any(String),
        language: "fr",
      });
    });

    it("should deny unauthenticated stream signup", async () => {
      const res = await client.users["sign-up-to-stream"].$post({});

      expect(res.status).toBe(401);
      expect(mockUpsertUser).not.toHaveBeenCalled();
    });
  });

  describe("POST /profile/avatar - Upload Avatar", () => {
    it("should upload avatar for owner user", async () => {
      const res = await client.users.profile.avatar.$post(
        {
          json: {
            content: "base64-encoded-image-data",
            contentType: "image/jpeg",
          },
        },
        {
          headers: {
            Authorization: `Bearer ${ownerUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.message).toBe("Avatar updated successfully");
      expect(data.profile).toBeDefined();
      expect(data.profile.avatar_url).toContain("https://assets.xtablo.com/");

      // Verify S3 PutObjectCommand was called
      expect(s3Mock.calls()).toHaveLength(1);
    });

    it("should upload avatar for temp user", async () => {
      const res = await client.users.profile.avatar.$post(
        {
          json: {
            content: "base64-encoded-image-data",
            contentType: "image/png",
          },
        },
        {
          headers: {
            Authorization: `Bearer ${temporaryUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.message).toBe("Avatar updated successfully");
      expect(data.profile).toBeDefined();
      expect(data.profile.avatar_url).toContain("https://assets.xtablo.com/");
    });

    it("should reject avatar upload without content", async () => {
      const res = await client.users.profile.avatar.$post(
        {
          json: {
            contentType: "image/jpeg",
          },
        },
        {
          headers: {
            Authorization: `Bearer ${ownerUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(400);
      const data = await res.json();
      expect(data.error).toBe("Content is required");
    });

    it("should deny unauthenticated avatar upload", async () => {
      const res = await client.users.profile.avatar.$post({
        json: {
          content: "base64-encoded-image-data",
          contentType: "image/jpeg",
        },
      });

      expect(res.status).toBe(401);
    });
  });

  describe("DELETE /profile/avatar - Delete Avatar", () => {
    it("should delete avatar for owner user", async () => {
      const res = await client.users.profile.avatar.$delete(
        {},
        {
          headers: {
            Authorization: `Bearer ${ownerUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.message).toBe("Avatar deleted successfully");

      // Verify S3 commands were called (ListObjectsV2 and DeleteObjects)
      expect(s3Mock.calls()).toHaveLength(2);
    });

    it("should delete avatar for temp user", async () => {
      const res = await client.users.profile.avatar.$delete(
        {},
        {
          headers: {
            Authorization: `Bearer ${temporaryUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.message).toBe("Avatar deleted successfully");
    });

    it("should handle no avatar found gracefully", async () => {
      // Mock empty S3 response
      s3Mock.on(ListObjectsV2Command).resolves({ Contents: [] });

      const res = await client.users.profile.avatar.$delete(
        {},
        {
          headers: {
            Authorization: `Bearer ${ownerUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );

      expect(res.status).toBe(404);
      const data = await res.json();
      expect(data.error).toBe("No objects found");
    });

    it("should deny unauthenticated avatar deletion", async () => {
      const res = await client.users.profile.avatar.$delete({});

      expect(res.status).toBe(401);
    });
  });
});
