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";

// 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 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", 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);
    });

    it("should return temp user profile", 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);
    });

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

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

  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);
    });
  });

  // DELETE /me must run last — it hard-deletes the auth user, making ownerUser unusable for subsequent tests
  describe("DELETE /me - Delete Account", () => {
    it("should return 401 when unauthenticated", async () => {
      const res = await client.users.me.$delete({});
      expect(res.status).toBe(401);
    });

    it("should delete the authenticated user's account", async () => {
      const res = await client.users.me.$delete(
        {},
        {
          headers: {
            Authorization: `Bearer ${ownerUser.accessToken}`,
            "Content-Type": "application/json",
          },
        }
      );
      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data).toEqual({ message: "Account deleted successfully" });
    });
  });
});
