import { describe, expect, it, vi } from "vitest";
import { appRouter } from "./routers";
import type { TrpcContext } from "./_core/context";

type AuthenticatedUser = NonNullable<TrpcContext["user"]>;

/* ─── Context helpers for each role ─── */
function createContext(role: "admin" | "agent" | "user" | null): TrpcContext {
  if (!role) {
    return {
      user: null,
      req: { protocol: "https", headers: {} } as TrpcContext["req"],
      res: { clearCookie: vi.fn() } as unknown as TrpcContext["res"],
    };
  }

  const user: AuthenticatedUser = {
    id: role === "admin" ? 1 : role === "agent" ? 2 : 3,
    openId: `${role}-openid`,
    email: `${role}@homelegance.com`,
    name: `Test ${role.charAt(0).toUpperCase() + role.slice(1)}`,
    loginMethod: "manus",
    role,
    createdAt: new Date(),
    updatedAt: new Date(),
    lastSignedIn: new Date(),
  };

  return {
    user,
    req: { protocol: "https", headers: {} } as TrpcContext["req"],
    res: { clearCookie: vi.fn() } as unknown as TrpcContext["res"],
  };
}

/* ─── Mock database ─── */
vi.mock("./db", () => {
  const users = [
    { id: 1, openId: "admin-openid", name: "Test Admin", email: "admin@homelegance.com", role: "admin", lastSignedIn: new Date(), createdAt: new Date(), updatedAt: new Date() },
    { id: 2, openId: "agent-openid", name: "Test Agent", email: "agent@homelegance.com", role: "agent", lastSignedIn: new Date(), createdAt: new Date(), updatedAt: new Date() },
    { id: 3, openId: "user-openid", name: "Test User", email: "user@homelegance.com", role: "user", lastSignedIn: new Date(), createdAt: new Date(), updatedAt: new Date() },
  ];

  return {
    createConversation: vi.fn(async (data: any) => ({ id: 1, ...data, createdAt: new Date(), updatedAt: new Date() })),
    getConversations: vi.fn(async () => []),
    getConversationById: vi.fn(async (id: number) => ({ id, sessionId: "test", status: "active", createdAt: new Date(), updatedAt: new Date() })),
    getConversationBySessionId: vi.fn(async () => null),
    updateConversationStatus: vi.fn(async (id: number, status: string) => ({ id, status })),
    getConversationStats: vi.fn(async () => ({ total: 5, active: 2, escalated: 1, resolved: 1, closed: 1 })),
    addMessage: vi.fn(async (data: any) => ({ id: 1, ...data, createdAt: new Date() })),
    getMessagesByConversation: vi.fn(async () => []),
    saveWorkflow: vi.fn(async (wid: string, nodes: any[], edges: any[]) => ({ workflowId: wid, nodeCount: nodes.length, edgeCount: edges.length })),
    getWorkflow: vi.fn(async () => ({ nodes: [], edges: [] })),
    getAllUsers: vi.fn(async () => users),
    updateUserRole: vi.fn(async (userId: number, role: string) => {
      const u = users.find(u => u.id === userId);
      if (!u) return null;
      return { ...u, role };
    }),
    getUserById: vi.fn(async (userId: number) => users.find(u => u.id === userId)),
    getUserByEmail: vi.fn(async (email: string) => users.find(u => u.email === email) || null),
    deleteUser: vi.fn(async (userId: number) => users.find(u => u.id === userId) || null),
    createInvitation: vi.fn(async (data: any) => ({ id: 1, ...data, createdAt: new Date() })),
    getAllInvitations: vi.fn(async () => []),
    getInvitationByToken: vi.fn(async () => null),
    getInvitationByEmail: vi.fn(async () => []),
    updateInvitationStatus: vi.fn(async (id: number, status: string) => ({ id, status })),
    expireOldInvitations: vi.fn(async () => {}),
    createAuditLog: vi.fn(async (data: any) => ({ id: 1, ...data, createdAt: new Date() })),
    getAuditLogs: vi.fn(async () => []),
    getConversationsAdvanced: vi.fn(async () => ({ conversations: [], total: 0, page: 1, pageSize: 20, totalPages: 0 })),
    getConversationMessageCounts: vi.fn(async () => ({})),
    getAgentUsers: vi.fn(async () => []),
    bulkUpdateConversationStatus: vi.fn(async (ids: number[]) => ({ updated: ids.length })),
    deleteConversations: vi.fn(async (ids: number[]) => ({ deleted: ids.length })),
    getUserByEmailWithPassword: vi.fn(async () => null),
    createUserWithPassword: vi.fn(async (data: any) => ({ id: 10, ...data })),
    updateUserPassword: vi.fn(async () => {}),
    createPasswordResetToken: vi.fn(async (data: any) => ({ id: 1, ...data })),
    getPasswordResetToken: vi.fn(async () => null),
    markPasswordResetTokenUsed: vi.fn(async () => {}),
    upsertUser: vi.fn(),
    getUserByOpenId: vi.fn(),
  };
});

vi.mock("./_core/llm", () => ({
  invokeLLM: vi.fn(async () => ({
    id: "test",
    created: Date.now(),
    model: "test",
    choices: [{ index: 0, message: { role: "assistant", content: "Test response" }, finish_reason: "stop" }],
  })),
}));

/* ═══════════════════════════════════════════════════════════════
   AGENT PROCEDURES — require agent or admin role
   ═══════════════════════════════════════════════════════════════ */

describe("Agent procedures — role-based access", () => {
  describe("Unauthenticated users", () => {
    it("rejects agent.stats for unauthenticated user", async () => {
      const caller = appRouter.createCaller(createContext(null));
      await expect(caller.agent.stats()).rejects.toThrow();
    });

    it("rejects agent.conversations for unauthenticated user", async () => {
      const caller = appRouter.createCaller(createContext(null));
      await expect(caller.agent.conversations({})).rejects.toThrow();
    });

    it("rejects agent.reply for unauthenticated user", async () => {
      const caller = appRouter.createCaller(createContext(null));
      await expect(caller.agent.reply({ conversationId: 1, content: "Hi" })).rejects.toThrow();
    });
  });

  describe("Regular users (role=user)", () => {
    it("rejects agent.stats for regular user", async () => {
      const caller = appRouter.createCaller(createContext("user"));
      await expect(caller.agent.stats()).rejects.toThrow("Agent or admin access required");
    });

    it("rejects agent.conversations for regular user", async () => {
      const caller = appRouter.createCaller(createContext("user"));
      await expect(caller.agent.conversations({})).rejects.toThrow("Agent or admin access required");
    });

    it("rejects agent.reply for regular user", async () => {
      const caller = appRouter.createCaller(createContext("user"));
      await expect(caller.agent.reply({ conversationId: 1, content: "Hi" })).rejects.toThrow("Agent or admin access required");
    });

    it("rejects agent.messages for regular user", async () => {
      const caller = appRouter.createCaller(createContext("user"));
      await expect(caller.agent.messages({ conversationId: 1 })).rejects.toThrow("Agent or admin access required");
    });

    it("rejects agent.updateStatus for regular user", async () => {
      const caller = appRouter.createCaller(createContext("user"));
      await expect(caller.agent.updateStatus({ conversationId: 1, status: "resolved" })).rejects.toThrow("Agent or admin access required");
    });
  });

  describe("Agent users (role=agent)", () => {
    it("allows agent.stats for agent user", async () => {
      const caller = appRouter.createCaller(createContext("agent"));
      const stats = await caller.agent.stats();
      expect(stats).toHaveProperty("total");
      expect(stats).toHaveProperty("active");
      expect(stats).toHaveProperty("escalated");
    });

    it("allows agent.conversations for agent user", async () => {
      const caller = appRouter.createCaller(createContext("agent"));
      const result = await caller.agent.conversations({});
      expect(Array.isArray(result)).toBe(true);
    });

    it("allows agent.messages for agent user", async () => {
      const caller = appRouter.createCaller(createContext("agent"));
      const result = await caller.agent.messages({ conversationId: 1 });
      expect(Array.isArray(result)).toBe(true);
    });

    it("allows agent.reply for agent user", async () => {
      const caller = appRouter.createCaller(createContext("agent"));
      const result = await caller.agent.reply({ conversationId: 1, content: "I can help you" });
      expect(result).toHaveProperty("id");
      expect(result.content).toBe("I can help you");
    });
  });

  describe("Admin users (role=admin)", () => {
    it("allows agent.stats for admin user", async () => {
      const caller = appRouter.createCaller(createContext("admin"));
      const stats = await caller.agent.stats();
      expect(stats.total).toBe(5);
    });

    it("allows agent.conversations for admin user", async () => {
      const caller = appRouter.createCaller(createContext("admin"));
      const result = await caller.agent.conversations({});
      expect(Array.isArray(result)).toBe(true);
    });
  });
});

/* ═══════════════════════════════════════════════════════════════
   ADMIN PROCEDURES — require admin role only
   ═══════════════════════════════════════════════════════════════ */

describe("Admin procedures — role-based access", () => {
  describe("Users management", () => {
    it("rejects users.list for unauthenticated user", async () => {
      const caller = appRouter.createCaller(createContext(null));
      await expect(caller.users.list()).rejects.toThrow();
    });

    it("rejects users.list for regular user", async () => {
      const caller = appRouter.createCaller(createContext("user"));
      await expect(caller.users.list()).rejects.toThrow();
    });

    it("rejects users.list for agent user", async () => {
      const caller = appRouter.createCaller(createContext("agent"));
      await expect(caller.users.list()).rejects.toThrow();
    });

    it("allows users.list for admin user", async () => {
      const caller = appRouter.createCaller(createContext("admin"));
      const result = await caller.users.list();
      expect(Array.isArray(result)).toBe(true);
      expect(result.length).toBe(3);
    });

    it("allows users.updateRole for admin user", async () => {
      const caller = appRouter.createCaller(createContext("admin"));
      const result = await caller.users.updateRole({ userId: 2, role: "admin" });
      expect(result).toHaveProperty("role", "admin");
    });

    it("prevents admin from changing own role", async () => {
      const caller = appRouter.createCaller(createContext("admin"));
      await expect(
        caller.users.updateRole({ userId: 1, role: "user" })
      ).rejects.toThrow("You cannot change your own role");
    });

    it("rejects users.updateRole for agent user", async () => {
      const caller = appRouter.createCaller(createContext("agent"));
      await expect(
        caller.users.updateRole({ userId: 3, role: "agent" })
      ).rejects.toThrow();
    });

    it("allows users.getById for admin user", async () => {
      const caller = appRouter.createCaller(createContext("admin"));
      const result = await caller.users.getById({ userId: 2 });
      expect(result).toHaveProperty("name", "Test Agent");
    });
  });

  describe("Workflow management", () => {
    it("rejects workflow.save for unauthenticated user", async () => {
      const caller = appRouter.createCaller(createContext(null));
      await expect(
        caller.workflow.save({ workflowId: "test", nodes: [], edges: [] })
      ).rejects.toThrow();
    });

    it("rejects workflow.save for regular user", async () => {
      const caller = appRouter.createCaller(createContext("user"));
      await expect(
        caller.workflow.save({ workflowId: "test", nodes: [], edges: [] })
      ).rejects.toThrow();
    });

    it("rejects workflow.save for agent user", async () => {
      const caller = appRouter.createCaller(createContext("agent"));
      await expect(
        caller.workflow.save({ workflowId: "test", nodes: [], edges: [] })
      ).rejects.toThrow();
    });

    it("allows workflow.save for admin user", async () => {
      const caller = appRouter.createCaller(createContext("admin"));
      const result = await caller.workflow.save({
        workflowId: "test",
        nodes: [{
          workflowId: "test",
          nodeId: "n1",
          type: "greeting",
          label: "Welcome",
          config: {},
          positionX: 100,
          positionY: 100,
        }],
        edges: [],
      });
      expect(result.workflowId).toBe("test");
      expect(result.nodeCount).toBe(1);
    });

    it("rejects workflow.load for agent user", async () => {
      const caller = appRouter.createCaller(createContext("agent"));
      await expect(
        caller.workflow.load({ workflowId: "test" })
      ).rejects.toThrow();
    });

    it("allows workflow.load for admin user", async () => {
      const caller = appRouter.createCaller(createContext("admin"));
      const result = await caller.workflow.load({ workflowId: "test" });
      expect(result).toHaveProperty("nodes");
      expect(result).toHaveProperty("edges");
    });
  });
});

/* ═══════════════════════════════════════════════════════════════
   PUBLIC PROCEDURES — accessible without auth
   ═══════════════════════════════════════════════════════════════ */

describe("Public procedures — no auth required", () => {
  it("allows auth.me without authentication", async () => {
    const caller = appRouter.createCaller(createContext(null));
    const result = await caller.auth.me();
    expect(result).toBeNull();
  });

  it("returns user for authenticated auth.me", async () => {
    const caller = appRouter.createCaller(createContext("admin"));
    const result = await caller.auth.me();
    expect(result).toHaveProperty("name", "Test Admin");
    expect(result).toHaveProperty("role", "admin");
  });

  it("allows chat.getMessages without authentication", async () => {
    const caller = appRouter.createCaller(createContext(null));
    const result = await caller.chat.getMessages({ sessionId: "test" });
    expect(result).toHaveProperty("messages");
  });
});
