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

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

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

function createAuthContext(): TrpcContext {
  const user: AuthenticatedUser = {
    id: 1,
    openId: "agent-user",
    email: "agent@homelegance.com",
    name: "Test Agent",
    loginMethod: "manus",
    role: "admin",
    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 the database module
vi.mock("./db", () => {
  const conversations = new Map<string, any>();
  const messageStore = new Map<number, any[]>();
  let convIdCounter = 1;
  let msgIdCounter = 1;

  return {
    createConversation: vi.fn(async (data: any) => {
      const id = convIdCounter++;
      const conv = { id, ...data, createdAt: new Date(), updatedAt: new Date() };
      conversations.set(data.sessionId, conv);
      messageStore.set(id, []);
      return conv;
    }),
    getConversations: vi.fn(async (status?: string) => {
      const all = Array.from(conversations.values());
      if (status) return all.filter((c) => c.status === status);
      return all;
    }),
    getConversationById: vi.fn(async (id: number) => {
      return Array.from(conversations.values()).find((c) => c.id === id);
    }),
    getConversationBySessionId: vi.fn(async (sessionId: string) => {
      return conversations.get(sessionId);
    }),
    updateConversationStatus: vi.fn(async (id: number, status: string, agentId?: number) => {
      const conv = Array.from(conversations.values()).find((c) => c.id === id);
      if (conv) {
        conv.status = status;
        if (agentId) conv.assignedAgentId = agentId;
        conversations.set(conv.sessionId, conv);
      }
      return conv;
    }),
    getConversationStats: vi.fn(async () => ({
      total: conversations.size,
      active: Array.from(conversations.values()).filter((c) => c.status === "active").length,
      escalated: Array.from(conversations.values()).filter((c) => c.status === "escalated").length,
      resolved: 0,
      closed: 0,
    })),
    addMessage: vi.fn(async (data: any) => {
      const id = msgIdCounter++;
      const msg = { id, ...data, createdAt: new Date() };
      const msgs = messageStore.get(data.conversationId) || [];
      msgs.push(msg);
      messageStore.set(data.conversationId, msgs);
      return msg;
    }),
    getMessagesByConversation: vi.fn(async (conversationId: number) => {
      return messageStore.get(conversationId) || [];
    }),
    saveWorkflow: vi.fn(async (workflowId: string, nodes: any[], edges: any[]) => ({
      workflowId,
      nodeCount: nodes.length,
      edgeCount: edges.length,
    })),
    getWorkflow: vi.fn(async () => ({ nodes: [], edges: [] })),
    getWorkflowSuggestions: vi.fn(async () => []),
    updateWorkflowSuggestionStatus: vi.fn(async (id: number, status: string, reviewedById: number) => ({ id, status })),
    bulkCreateWorkflowSuggestions: vi.fn(async (suggestions: any[]) => ({ created: suggestions.length })),
    getDb: vi.fn(async () => ({
      select: vi.fn().mockReturnThis(),
      from: vi.fn().mockReturnThis(),
      where: vi.fn().mockReturnThis(),
      orderBy: vi.fn().mockReturnThis(),
      limit: vi.fn(async () => [
        { content: "What is the shipping time?", sender: "visitor" },
        { content: "How do I check my order status?", sender: "visitor" },
        { content: "Do you have modern bedroom sets?", sender: "visitor" },
      ]),
    })),
    upsertUser: vi.fn(),
    getUserByOpenId: vi.fn(),
    // Analytics
    trackAnalyticsEvent: vi.fn(async (data: any) => ({ id: 1, ...data, createdAt: new Date() })),
    getAnalyticsEvents: vi.fn(async () => []),
    getAnalyticsSummary: vi.fn(async () => ({
      totalConversations: 150,
      resolvedByBot: 95,
      escalatedToAgent: 40,
      abandoned: 15,
      avgResponseTime: 1.8,
      resolutionRate: 63.3,
      topIntents: [
        { intent: "order_status", count: 45 },
        { intent: "shipping", count: 35 },
        { intent: "returns", count: 25 },
      ],
    })),
    // Data Sources
    getDataSources: vi.fn(async () => []),
    createDataSource: vi.fn(async (data: any) => ({ id: 1, ...data, status: "active", createdAt: new Date(), updatedAt: new Date() })),
    updateDataSource: vi.fn(async (id: number, data: any) => ({ id, ...data })),
    deleteDataSource: vi.fn(async () => true),
    // API Connections
    getApiConnections: vi.fn(async () => []),
    createApiConnection: vi.fn(async (data: any) => ({ id: 1, ...data, status: "active", createdAt: new Date(), updatedAt: new Date() })),
    updateApiConnection: vi.fn(async (id: number, data: any) => ({ id, ...data })),
    deleteApiConnection: vi.fn(async () => true),
    testApiConnection: vi.fn(async () => ({ success: true, responseTime: 120, statusCode: 200 })),
  };
});

// Mock the LLM module
vi.mock("./_core/llm", () => ({
  invokeLLM: vi.fn(async () => ({
    id: "test",
    created: Date.now(),
    model: "test",
    choices: [
      {
        index: 0,
        message: { role: "assistant", content: "I can help you find furniture! What style are you looking for?" },
        finish_reason: "stop",
      },
    ],
  })),
}));

describe("chat.startSession", () => {
  it("creates a new conversation and returns sessionId", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.chat.startSession({});

    expect(result).toHaveProperty("sessionId");
    expect(result).toHaveProperty("conversationId");
    expect(typeof result.sessionId).toBe("string");
    expect(result.sessionId.length).toBeGreaterThan(0);
  });

  it("creates a session with visitor name", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.chat.startSession({ visitorName: "John Doe" });

    expect(result.sessionId).toBeTruthy();
    expect(result.conversationId).toBeGreaterThan(0);
  });
});

describe("chat.sendMessage", () => {
  it("sends a message and receives an AI response", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    // Start a session first
    const session = await caller.chat.startSession({});

    // Send a message
    const result = await caller.chat.sendMessage({
      sessionId: session.sessionId,
      content: "I'm looking for a sofa",
    });

    expect(result).toHaveProperty("reply");
    expect(result.reply).toBeTruthy();
    expect(typeof result.reply).toBe("string");
  });

  it("triggers escalation when user asks for human agent", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    const session = await caller.chat.startSession({});

    const result = await caller.chat.sendMessage({
      sessionId: session.sessionId,
      content: "I want to speak to human agent please",
    });

    expect(result.status).toBe("escalated");
    expect(result.reply).toContain("team member");
  });

  it("throws error for invalid session", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.chat.sendMessage({ sessionId: "nonexistent", content: "Hello" })
    ).rejects.toThrow("Conversation not found");
  });
});

describe("chat.getMessages", () => {
  it("returns messages for a valid session", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    const session = await caller.chat.startSession({});
    const result = await caller.chat.getMessages({ sessionId: session.sessionId });

    expect(result).toHaveProperty("messages");
    expect(result).toHaveProperty("status");
    expect(Array.isArray(result.messages)).toBe(true);
  });

  it("returns empty for invalid session", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.chat.getMessages({ sessionId: "nonexistent" });
    expect(result.messages).toEqual([]);
    expect(result.status).toBe("closed");
  });
});

describe("agent.stats", () => {
  it("requires authentication", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(caller.agent.stats()).rejects.toThrow();
  });

  it("returns stats for authenticated user", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const stats = await caller.agent.stats();
    expect(stats).toHaveProperty("total");
    expect(stats).toHaveProperty("active");
    expect(stats).toHaveProperty("escalated");
    expect(stats).toHaveProperty("resolved");
    expect(stats).toHaveProperty("closed");
  });
});

describe("agent.reply", () => {
  it("requires authentication", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.agent.reply({ conversationId: 1, content: "Hello" })
    ).rejects.toThrow();
  });
});

describe("workflow.save", () => {
  it("requires authentication", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.workflow.save({
        workflowId: "test",
        nodes: [],
        edges: [],
      })
    ).rejects.toThrow();
  });

  it("saves workflow for authenticated user", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.save({
      workflowId: "test-workflow",
      nodes: [
        {
          workflowId: "test-workflow",
          nodeId: "n1",
          type: "greeting",
          label: "Welcome",
          config: { message: "Hello!" },
          positionX: 100,
          positionY: 100,
        },
      ],
      edges: [],
    });

    expect(result.workflowId).toBe("test-workflow");
    expect(result.nodeCount).toBe(1);
    expect(result.edgeCount).toBe(0);
  });
});

describe("workflow.load", () => {
  it("loads workflow for authenticated user", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.load({ workflowId: "test-workflow" });
    expect(result).toHaveProperty("nodes");
    expect(result).toHaveProperty("edges");
  });
});

describe("workflow.save with new node types", () => {
  it("saves customer_data node", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.save({
      workflowId: "test-workflow",
      nodes: [
        {
          workflowId: "test-workflow",
          nodeId: "n_cust",
          type: "customer_data",
          label: "Customer Lookup",
          config: { apiEndpoint: "/api/crm/customer", lookupField: "customerId", returnFields: ["name", "email"] },
          positionX: 100,
          positionY: 200,
        },
      ],
      edges: [],
    });

    expect(result.nodeCount).toBe(1);
  });

  it("saves sales_order node", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.save({
      workflowId: "test-workflow",
      nodes: [
        {
          workflowId: "test-workflow",
          nodeId: "n_ord",
          type: "sales_order",
          label: "Order History",
          config: { apiEndpoint: "/api/orders/history", lookupField: "customerId" },
          positionX: 200,
          positionY: 300,
        },
      ],
      edges: [],
    });

    expect(result.nodeCount).toBe(1);
  });

  it("saves guardrail node with blocked topics", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.save({
      workflowId: "test-workflow",
      nodes: [
        {
          workflowId: "test-workflow",
          nodeId: "n_guard",
          type: "guardrail",
          label: "Content Filter",
          config: {
            blockedTopics: ["revenue", "margin", "internal pricing"],
            blockedMessage: "Sorry, I cannot share that information.",
          },
          positionX: 300,
          positionY: 100,
        },
      ],
      edges: [],
    });

    expect(result.nodeCount).toBe(1);
  });

  it("saves greeting node with customer ID detection config", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.save({
      workflowId: "test-workflow",
      nodes: [
        {
          workflowId: "test-workflow",
          nodeId: "n_greet",
          type: "greeting",
          label: "Welcome & Identify",
          config: {
            message: "Welcome!",
            detectCustomerId: true,
            customerIdSource: "session",
            personalizedGreeting: "Welcome back, {{customerName}}!",
          },
          positionX: 400,
          positionY: 50,
        },
      ],
      edges: [],
    });

    expect(result.nodeCount).toBe(1);
  });

  it("saves mixed workflow with all new node types", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.save({
      workflowId: "full-workflow",
      nodes: [
        { workflowId: "full-workflow", nodeId: "n1", type: "greeting", label: "Welcome", config: { detectCustomerId: true }, positionX: 0, positionY: 0 },
        { workflowId: "full-workflow", nodeId: "n2", type: "guardrail", label: "Filter", config: { blockedTopics: ["revenue"] }, positionX: 100, positionY: 0 },
        { workflowId: "full-workflow", nodeId: "n3", type: "customer_data", label: "CRM", config: { apiEndpoint: "/api/crm" }, positionX: 200, positionY: 0 },
        { workflowId: "full-workflow", nodeId: "n4", type: "sales_order", label: "Orders", config: { apiEndpoint: "/api/orders" }, positionX: 300, positionY: 0 },
        { workflowId: "full-workflow", nodeId: "n5", type: "intent", label: "Detect", config: { intents: ["search"] }, positionX: 400, positionY: 0 },
        { workflowId: "full-workflow", nodeId: "n6", type: "end", label: "End", config: {}, positionX: 500, positionY: 0 },
      ],
      edges: [
        { workflowId: "full-workflow", sourceNodeId: "n1", targetNodeId: "n2" },
        { workflowId: "full-workflow", sourceNodeId: "n2", targetNodeId: "n3" },
        { workflowId: "full-workflow", sourceNodeId: "n3", targetNodeId: "n4" },
        { workflowId: "full-workflow", sourceNodeId: "n4", targetNodeId: "n5" },
        { workflowId: "full-workflow", sourceNodeId: "n5", targetNodeId: "n6" },
      ],
    });

    expect(result.nodeCount).toBe(6);
    expect(result.edgeCount).toBe(5);
  });
});

describe("workflow.getSuggestions", () => {
  it("requires authentication", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.workflow.getSuggestions({ workflowId: "test" })
    ).rejects.toThrow();
  });

  it("returns suggestions for authenticated admin", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.getSuggestions({ workflowId: "test-workflow" });
    expect(Array.isArray(result)).toBe(true);
  });

  it("accepts optional status filter", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.getSuggestions({ workflowId: "test-workflow", status: "pending" });
    expect(Array.isArray(result)).toBe(true);
  });
});

describe("workflow.reviewSuggestion", () => {
  it("requires authentication", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.workflow.reviewSuggestion({ suggestionId: 1, status: "approved" })
    ).rejects.toThrow();
  });

  it("approves a suggestion", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.reviewSuggestion({ suggestionId: 1, status: "approved" });
    expect(result.id).toBe(1);
    expect(result.status).toBe("approved");
  });

  it("declines a suggestion", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.reviewSuggestion({ suggestionId: 2, status: "declined" });
    expect(result.id).toBe(2);
    expect(result.status).toBe("declined");
  });

  it("sets a suggestion to waiting", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.reviewSuggestion({ suggestionId: 3, status: "waiting" });
    expect(result.id).toBe(3);
    expect(result.status).toBe("waiting");
  });
});

describe("workflow.generateSuggestions", () => {
  it("requires authentication", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.workflow.generateSuggestions({ workflowId: "test" })
    ).rejects.toThrow();
  });

  it("generates suggestions from conversation data", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.workflow.generateSuggestions({ workflowId: "test-workflow" });
    expect(result).toHaveProperty("suggestions");
    expect(result).toHaveProperty("message");
  });
});


/* ─── Analytics Tests ─── */
describe("analytics.summary", () => {
  it("requires admin authentication", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.analytics.summary({})
    ).rejects.toThrow();
  });

  it("returns analytics summary data", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.analytics.summary({});
    expect(result).toHaveProperty("totalConversations");
    expect(result).toHaveProperty("resolvedByBot");
    expect(result).toHaveProperty("resolutionRate");
    expect(result).toHaveProperty("topIntents");
    expect(result.totalConversations).toBe(150);
    expect(result.resolutionRate).toBeCloseTo(63.3);
  });
});

describe("analytics.track", () => {
  it("tracks an analytics event", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.analytics.track({
      eventType: "resolved_by_bot",
      conversationId: 1,
      metadata: { intent: "order_status" },
    });
    expect(result).toHaveProperty("id");
  });
});

/* ─── Data Sources Tests ─── */
describe("dataSources.list", () => {
  it("requires admin authentication", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.dataSources.list()
    ).rejects.toThrow();
  });

  it("returns list of data sources", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.dataSources.list();
    expect(Array.isArray(result)).toBe(true);
  });
});

describe("dataSources.create", () => {
  it("creates a new data source", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.dataSources.create({
      name: "Product Catalog",
      type: "url",
      config: { url: "https://api.homelegance.com/products" },
    });
    expect(result).toHaveProperty("id");
  });
});

describe("dataSources.delete", () => {
  it("deletes a data source", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.dataSources.delete({ id: 1 });
    expect(result).toHaveProperty("success");
  });
});

/* ─── API Connections Tests ─── */
describe("apiConnections.list", () => {
  it("requires admin authentication", async () => {
    const ctx = createPublicContext();
    const caller = appRouter.createCaller(ctx);

    await expect(
      caller.apiConnections.list()
    ).rejects.toThrow();
  });

  it("returns list of API connections", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.apiConnections.list();
    expect(Array.isArray(result)).toBe(true);
  });
});

describe("apiConnections.create", () => {
  it("creates a new API connection", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.apiConnections.create({
      name: "Order Status API",
      description: "Check order status",
      category: "orders",
      method: "GET",
      endpoint: "https://api.homelegance.com/orders/{orderId}",
      headers: { Authorization: "Bearer {{API_KEY}}" },
      inputVariables: [{ name: "orderId", type: "string", description: "Order ID" }],
      outputVariables: [{ name: "status", type: "string", description: "Order status" }],
    });
    expect(result).toHaveProperty("id");
  });
});

describe("apiConnections.delete", () => {
  it("deletes an API connection", async () => {
    const ctx = createAuthContext();
    const caller = appRouter.createCaller(ctx);

    const result = await caller.apiConnections.delete({ id: 1 });
    expect(result).toHaveProperty("success");
  });
});
