import {
  DeleteObjectsCommand,
  ListObjectsV2Command,
  PutObjectCommand,
  S3Client,
} from "@aws-sdk/client-s3";
import { createClient } from "@supabase/supabase-js";
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);
    });
  });

  describe("GET /organization - Billing State", () => {
    it("returns Apple-backed active access for the authenticated owner", async () => {
      const adminClient = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY, {
        auth: {
          autoRefreshToken: false,
          persistSession: false,
        },
      });
      const organizationName = `Apple Billing Org ${Date.now()}`;
      const originalTransactionId = `org-apple-${Date.now()}`;

      const { data: organization, error: organizationError } = await adminClient
        .from("organizations")
        .insert({
          name: organizationName,
        })
        .select("id")
        .single();
      expect(organizationError).toBeNull();

      const organizationId = organization?.id as number;
      const { error: profileUpdateError } = await adminClient
        .from("profiles")
        .update({ organization_id: organizationId })
        .eq("id", ownerUser.userId);
      expect(profileUpdateError).toBeNull();

      await adminClient.from("apple_subscriptions").delete().eq("owner_user_id", ownerUser.userId);
      await adminClient.from("apple_customers").delete().eq("user_id", ownerUser.userId);

      const { error: customerError } = await adminClient.from("apple_customers").insert({
        revenuecat_app_user_id: ownerUser.userId,
        user_id: ownerUser.userId,
      });
      expect(customerError).toBeNull();

      const { error: subscriptionError } = await adminClient.from("apple_subscriptions").insert({
        current_period_end: new Date(Date.now() + 86400000).toISOString(),
        current_period_start: new Date().toISOString(),
        environment: "SANDBOX",
        original_transaction_id: originalTransactionId,
        owner_user_id: ownerUser.userId,
        plan: "annual",
        revenuecat_app_user_id: ownerUser.userId,
        status: "active",
        store: "APP_STORE",
        store_product_id: "annual_ios",
      });
      expect(subscriptionError).toBeNull();

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

      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data.active_subscription_plan).toBe("annual");
      expect(data.is_billing_owner).toBe(true);

      await adminClient
        .from("profiles")
        .update({ organization_id: null })
        .eq("id", ownerUser.userId);
      await adminClient
        .from("apple_subscriptions")
        .delete()
        .eq("original_transaction_id", originalTransactionId);
      await adminClient.from("apple_customers").delete().eq("user_id", ownerUser.userId);
      await adminClient.from("organizations").delete().eq("id", organizationId);
    });
  });

  // 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 adminClient = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY, {
        auth: {
          autoRefreshToken: false,
          persistSession: false,
        },
      });
      const disposableEmail = `delete-me-${Date.now()}@example.com`;
      const disposablePassword = "test_password_123";

      const { data: authData, error: createUserError } = await adminClient.auth.admin.createUser({
        email: disposableEmail,
        password: disposablePassword,
        email_confirm: true,
        user_metadata: {
          first_name: "Delete",
          last_name: "Me",
          name: "Delete Me",
        },
      });

      expect(createUserError).toBeNull();
      expect(authData.user).toBeDefined();

      const authClient = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY, {
        auth: {
          autoRefreshToken: false,
          persistSession: false,
        },
      });
      const { data: signInData, error: signInError } = await authClient.auth.signInWithPassword({
        email: disposableEmail,
        password: disposablePassword,
      });

      expect(signInError).toBeNull();
      expect(signInData.session).toBeDefined();

      const res = await client.users.me.$delete(
        {},
        {
          headers: {
            Authorization: `Bearer ${signInData.session?.access_token}`,
            "Content-Type": "application/json",
          },
        }
      );
      expect(res.status).toBe(200);
      const data = await res.json();
      expect(data).toEqual({ message: "Account deleted successfully" });

      const { data: deletedUser } = await adminClient.auth.admin.getUserById(authData.user!.id);
      expect(deletedUser.user).toBeNull();
    });
  });
});
