import { describe, it, expect, vi, beforeEach } from "vitest";
import { initTRPC, TRPCError } from "@trpc/server";
import { wrap } from "@typeschema/valibot";
import {
  CreateRemovalRequestSchema,
  RequestStatusSchema,
  ScanListingsSchema,
  BrokerListingsFilterSchema,
  RemovalRequestsFilterSchema,
} from "../schemas/removebrokers";

vi.mock("~/server/services/removebrokers.service", () => ({
  getBrokerRegistry: vi.fn(),
  getRemovalRequests: vi.fn(),
  createRemovalRequest: vi.fn(),
  getRequestStatus: vi.fn(),
  getBrokerListings: vi.fn(),
  scanForListings: vi.fn(),
  getStats: vi.fn(),
}));

import * as removebrokersService from "~/server/services/removebrokers.service";

const mockGetBrokerRegistry = vi.mocked(removebrokersService.getBrokerRegistry);
const mockGetRemovalRequests = vi.mocked(removebrokersService.getRemovalRequests);
const mockCreateRemovalRequest = vi.mocked(removebrokersService.createRemovalRequest);
const mockGetRequestStatus = vi.mocked(removebrokersService.getRequestStatus);
const mockGetBrokerListings = vi.mocked(removebrokersService.getBrokerListings);
const mockScanForListings = vi.mocked(removebrokersService.scanForListings);
const mockGetStats = vi.mocked(removebrokersService.getStats);

type User = {
  id: string; email: string; name: string | null; image: string | null;
  role: string; emailVerified: Date | null; deletedAt: Date | null;
  stripeCustomerId: string | null;
  createdAt: Date; updatedAt: Date;
};
type Ctx = { db: object; user: User | null; apiKey: string | null };

function createCaller(user: User | null) {
  const t = initTRPC.context<Ctx>().create();
  const isAuthed = t.middleware(({ ctx, next }) => {
    if (!ctx.user) throw new TRPCError({ code: "UNAUTHORIZED" });
    return next({ ctx: { ...ctx, user: ctx.user } });
  });

  const router = t.router({
    getBrokerRegistry: t.procedure.use(isAuthed).query(async () => {
      return mockGetBrokerRegistry();
    }),
    getRemovalRequests: t.procedure.use(isAuthed)
      .input(wrap(RemovalRequestsFilterSchema))
      .query(async ({ input }) => {
        return mockGetRemovalRequests("user-1", input);
      }),
    createRemovalRequest: t.procedure.use(isAuthed)
      .input(wrap(CreateRemovalRequestSchema))
      .mutation(async ({ input }) => {
        return mockCreateRemovalRequest("user-1", input.brokerId, input.personalInfo);
      }),
    getRequestStatus: t.procedure.use(isAuthed)
      .input(wrap(RequestStatusSchema))
      .query(async ({ input }) => {
        return mockGetRequestStatus("user-1", input.requestId);
      }),
    getBrokerListings: t.procedure.use(isAuthed)
      .input(wrap(BrokerListingsFilterSchema))
      .query(async ({ input }) => {
        return mockGetBrokerListings("user-1", input);
      }),
    scanForListings: t.procedure.use(isAuthed)
      .input(wrap(ScanListingsSchema))
      .mutation(async ({ input }) => {
        return mockScanForListings("user-1", input.brokerId);
      }),
    getStats: t.procedure.use(isAuthed).query(async () => {
      return mockGetStats("user-1");
    }),
  });

  const caller = t.createCallerFactory(router);
  return caller({ db: {} as never, user, apiKey: null });
}

const baseUser: User = {
  id: "user-1", email: "a@b.com", name: "Test", image: null,
  role: "user", emailVerified: null, deletedAt: null,
  stripeCustomerId: null,
  createdAt: new Date(), updatedAt: new Date(),
};

function makeUser(overrides: Partial<User> = {}): User {
  return { ...baseUser, ...overrides };
}

beforeEach(() => {
  vi.clearAllMocks();
});

describe("removebrokers.getBrokerRegistry", () => {
  it("returns broker registry for authenticated user", async () => {
    const brokers = [{ name: "Spokeo", domain: "spokeo.com" }];
    mockGetBrokerRegistry.mockResolvedValue(brokers as never);
    const api = createCaller(makeUser());
    expect(await api.getBrokerRegistry()).toEqual(brokers);
  });

  it("rejects unauthenticated", async () => {
    const api = createCaller(null);
    await expect(api.getBrokerRegistry()).rejects.toThrow(TRPCError);
  });
});

describe("removebrokers.getRemovalRequests", () => {
  it("returns removal requests for authenticated user", async () => {
    const data = { items: [], total: 0, page: 1, limit: 20, totalPages: 0 };
    mockGetRemovalRequests.mockResolvedValue(data);
    const api = createCaller(makeUser());
    const result = await api.getRemovalRequests({ page: 1, limit: 20 });
    expect(result.total).toBe(0);
  });

  it("passes status filter", async () => {
    const data = { items: [], total: 0, page: 1, limit: 20, totalPages: 0 };
    mockGetRemovalRequests.mockResolvedValue(data);
    const api = createCaller(makeUser());
    await api.getRemovalRequests({ status: "PENDING" });
    expect(mockGetRemovalRequests).toHaveBeenCalledWith("user-1", { status: "PENDING", page: 1, limit: 20 });
  });
});

describe("removebrokers.createRemovalRequest", () => {
  it("creates a removal request", async () => {
    const request = { id: "r1", status: "PENDING" };
    mockCreateRemovalRequest.mockResolvedValue(request as never);
    const api = createCaller(makeUser());
    const result = await api.createRemovalRequest({
      brokerId: "broker-1",
      personalInfo: { fullName: "John Doe", email: "john@example.com" },
    });
    expect(result.id).toBe("r1");
  });

  it("rejects invalid input", async () => {
    const api = createCaller(makeUser());
    await expect(
      api.createRemovalRequest({ brokerId: "", personalInfo: { fullName: "" } } as never),
    ).rejects.toThrow();
  });
});

describe("removebrokers.getRequestStatus", () => {
  it("returns request status", async () => {
    const request = { id: "r1", status: "SUBMITTED", broker: null, listings: [] };
    mockGetRequestStatus.mockResolvedValue(request as never);
    const api = createCaller(makeUser());
    const result = await api.getRequestStatus({ requestId: "r1" });
    expect(result.status).toBe("SUBMITTED");
  });
});

describe("removebrokers.getBrokerListings", () => {
  it("returns broker listings", async () => {
    const data = { items: [], total: 0, page: 1, limit: 20, totalPages: 0 };
    mockGetBrokerListings.mockResolvedValue(data);
    const api = createCaller(makeUser());
    const result = await api.getBrokerListings({ page: 1, limit: 20 });
    expect(result.total).toBe(0);
  });
});

describe("removebrokers.scanForListings", () => {
  it("scans for listings", async () => {
    const result = { scanned: 1, listingsFound: 1, listings: [{ id: "l1", brokerId: "b1", url: "https://example.com" }] };
    mockScanForListings.mockResolvedValue(result);
    const api = createCaller(makeUser());
    const res = await api.scanForListings({ brokerId: "b1" });
    expect(res.scanned).toBe(1);
  });

  it("scans all brokers when no brokerId provided", async () => {
    mockScanForListings.mockResolvedValue({ scanned: 5, listingsFound: 3, listings: [] });
    const api = createCaller(makeUser());
    await api.scanForListings({});
    expect(mockScanForListings).toHaveBeenCalledWith("user-1", undefined);
  });
});

describe("removebrokers.getStats", () => {
  it("returns removal stats", async () => {
    const stats = { total: 5, byStatus: { COMPLETED: 2, PENDING: 3 }, totalListings: 10, listingsRemoved: 2, completionRate: 40 };
    mockGetStats.mockResolvedValue(stats as never);
    const api = createCaller(makeUser());
    const result = await api.getStats();
    expect(result.total).toBe(5);
  });
});
