import { describe, it, expect } from "vitest";
import {
  routeMarketplaceRequest,
  filterByModel,
  filterByStatus,
  filterByPrice,
  filterByReputation,
  filterByReputationTier,
  scoreNode,
  TIER_MIN_SCORES,
} from "@/lib/marketplace-router";
import type { MarketplaceNode } from "@/lib/marketplace-router";

function makeNode(overrides: Partial<MarketplaceNode> = {}): MarketplaceNode {
  return {
    deviceId: "dev-1",
    userId: "op-1",
    name: "Test Node",
    status: "online",
    tunnelUrl: "https://node1.example.com",
    models: ["qwen3:14b", "qwen3:8b"],
    pricePerMToken: 0.1,
    reputationScore: 85,
    latencyP95Ms: 500,
    uptimePercent: 99,
    ...overrides,
  };
}

describe("Marketplace Router", () => {
  describe("filterByModel", () => {
    it("should filter nodes with matching model", () => {
      const nodes = [
        makeNode({ deviceId: "a", models: ["qwen3:14b"] }),
        makeNode({ deviceId: "b", models: ["llama3.3:70b"] }),
      ];
      const result = filterByModel(nodes, "qwen3:14b");
      expect(result).toHaveLength(1);
      expect(result[0].deviceId).toBe("a");
    });

    it("should match case-insensitively", () => {
      const nodes = [makeNode({ models: ["Qwen3:14B"] })];
      const result = filterByModel(nodes, "qwen3:14b");
      expect(result).toHaveLength(1);
    });

    it("should match prefix", () => {
      const nodes = [makeNode({ models: ["qwen3:14b-q4_K_M"] })];
      const result = filterByModel(nodes, "qwen3:14b");
      expect(result).toHaveLength(1);
    });

    it("should return empty for no matches", () => {
      const nodes = [makeNode({ models: ["qwen3:14b"] })];
      const result = filterByModel(nodes, "llama3.3:70b");
      expect(result).toHaveLength(0);
    });

    it("should match any model in the node list", () => {
      const nodes = [makeNode({ models: ["qwen3:14b", "llama3.3:70b", "mistral:7b"] })];
      const result = filterByModel(nodes, "mistral:7b");
      expect(result).toHaveLength(1);
    });
  });

  describe("filterByStatus", () => {
    it("should keep only online nodes", () => {
      const nodes = [
        makeNode({ deviceId: "a", status: "online" }),
        makeNode({ deviceId: "b", status: "offline" }),
        makeNode({ deviceId: "c", status: "degraded" }),
      ];
      const result = filterByStatus(nodes);
      expect(result).toHaveLength(1);
      expect(result[0].deviceId).toBe("a");
    });

    it("should return empty if all offline", () => {
      const nodes = [
        makeNode({ status: "offline" }),
        makeNode({ status: "degraded" }),
      ];
      const result = filterByStatus(nodes);
      expect(result).toHaveLength(0);
    });
  });

  describe("filterByPrice", () => {
    it("should filter nodes above max price", () => {
      const nodes = [
        makeNode({ deviceId: "a", pricePerMToken: 0.05 }),
        makeNode({ deviceId: "b", pricePerMToken: 0.5 }),
      ];
      const result = filterByPrice(nodes, 0.1);
      expect(result).toHaveLength(1);
      expect(result[0].deviceId).toBe("a");
    });

    it("should include nodes at exactly max price", () => {
      const nodes = [makeNode({ pricePerMToken: 0.1 })];
      const result = filterByPrice(nodes, 0.1);
      expect(result).toHaveLength(1);
    });

    it("should return all when no max price", () => {
      const nodes = [makeNode(), makeNode()];
      const result = filterByPrice(nodes);
      expect(result).toHaveLength(2);
    });
  });

  describe("filterByReputation", () => {
    it("should filter nodes below minimum", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 90 }),
        makeNode({ deviceId: "b", reputationScore: 50 }),
      ];
      const result = filterByReputation(nodes, 70);
      expect(result).toHaveLength(1);
      expect(result[0].deviceId).toBe("a");
    });

    it("should include nodes at exactly minimum", () => {
      const nodes = [makeNode({ reputationScore: 70 })];
      const result = filterByReputation(nodes, 70);
      expect(result).toHaveLength(1);
    });

    it("should return all when no minimum", () => {
      const nodes = [makeNode({ reputationScore: 10 })];
      const result = filterByReputation(nodes);
      expect(result).toHaveLength(1);
    });
  });

  describe("scoreNode", () => {
    it("should score higher for better reputation", () => {
      const good = makeNode({ reputationScore: 95 });
      const bad = makeNode({ reputationScore: 30 });
      expect(scoreNode(good)).toBeGreaterThan(scoreNode(bad));
    });

    it("should score higher for lower price", () => {
      const cheap = makeNode({ pricePerMToken: 0.01 });
      const expensive = makeNode({ pricePerMToken: 0.99 });
      expect(scoreNode(cheap)).toBeGreaterThan(scoreNode(expensive));
    });

    it("should score higher for lower latency", () => {
      const fast = makeNode({ latencyP95Ms: 100 });
      const slow = makeNode({ latencyP95Ms: 4000 });
      expect(scoreNode(fast)).toBeGreaterThan(scoreNode(slow));
    });

    it("should score higher for better uptime", () => {
      const reliable = makeNode({ uptimePercent: 100 });
      const flaky = makeNode({ uptimePercent: 50 });
      expect(scoreNode(reliable)).toBeGreaterThan(scoreNode(flaky));
    });

    it("should weight latency more when preferLowLatency", () => {
      const fast = makeNode({ latencyP95Ms: 100 });
      const slow = makeNode({ latencyP95Ms: 4000 });
      const normalDiff = scoreNode(fast) - scoreNode(slow);
      const latencyDiff = scoreNode(fast, true) - scoreNode(slow, true);
      expect(latencyDiff).toBeGreaterThan(normalDiff);
    });

    it("should return non-negative score", () => {
      const worst = makeNode({
        reputationScore: 0,
        pricePerMToken: 1.0,
        latencyP95Ms: 5000,
        uptimePercent: 0,
      });
      expect(scoreNode(worst)).toBeGreaterThanOrEqual(0);
    });
  });

  describe("routeMarketplaceRequest", () => {
    it("should select best node", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 90, pricePerMToken: 0.05 }),
        makeNode({ deviceId: "b", reputationScore: 70, pricePerMToken: 0.2 }),
      ];
      const result = routeMarketplaceRequest(nodes, { model: "qwen3:14b" });
      expect(result).not.toBeNull();
      expect(result!.node.deviceId).toBe("a");
    });

    it("should return null when no nodes match model", () => {
      const nodes = [makeNode({ models: ["llama3.3:70b"] })];
      const result = routeMarketplaceRequest(nodes, { model: "qwen3:14b" });
      expect(result).toBeNull();
    });

    it("should return null when all nodes offline", () => {
      const nodes = [makeNode({ status: "offline" })];
      const result = routeMarketplaceRequest(nodes, { model: "qwen3:14b" });
      expect(result).toBeNull();
    });

    it("should respect max price filter", () => {
      const nodes = [
        makeNode({ deviceId: "a", pricePerMToken: 0.5 }),
        makeNode({ deviceId: "b", pricePerMToken: 0.05 }),
      ];
      const result = routeMarketplaceRequest(nodes, { model: "qwen3:14b", maxPricePerMToken: 0.1 });
      expect(result).not.toBeNull();
      expect(result!.node.deviceId).toBe("b");
    });

    it("should respect min reputation filter", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 50 }),
        makeNode({ deviceId: "b", reputationScore: 90 }),
      ];
      const result = routeMarketplaceRequest(nodes, { model: "qwen3:14b", minReputation: 80 });
      expect(result).not.toBeNull();
      expect(result!.node.deviceId).toBe("b");
    });

    it("should include reason in result", () => {
      const nodes = [makeNode()];
      const result = routeMarketplaceRequest(nodes, { model: "qwen3:14b" });
      expect(result!.reason).toContain("Selected");
      expect(result!.reason).toContain("score");
    });

    it("should include estimated cost", () => {
      const nodes = [makeNode({ pricePerMToken: 0.15 })];
      const result = routeMarketplaceRequest(nodes, { model: "qwen3:14b" });
      expect(result!.estimatedCostPerMToken).toBe(0.15);
    });

    it("should handle empty node list", () => {
      const result = routeMarketplaceRequest([], { model: "qwen3:14b" });
      expect(result).toBeNull();
    });

    it("should return null when all too expensive", () => {
      const nodes = [makeNode({ pricePerMToken: 1.0 })];
      const result = routeMarketplaceRequest(nodes, { model: "qwen3:14b", maxPricePerMToken: 0.5 });
      expect(result).toBeNull();
    });

    it("should prefer low latency when requested", () => {
      const nodes = [
        makeNode({ deviceId: "a", latencyP95Ms: 3000, reputationScore: 85 }),
        makeNode({ deviceId: "b", latencyP95Ms: 100, reputationScore: 80 }),
      ];
      const result = routeMarketplaceRequest(nodes, { model: "qwen3:14b", preferLowLatency: true });
      expect(result).not.toBeNull();
      expect(result!.node.deviceId).toBe("b");
    });

    it("should respect minReputationTier filter - Standard", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 30 }), // Unverified
        makeNode({ deviceId: "b", reputationScore: 60 }), // Standard
        makeNode({ deviceId: "c", reputationScore: 90 }), // Verified
      ];
      const result = routeMarketplaceRequest(nodes, {
        model: "qwen3:14b",
        minReputationTier: "Standard",
      });
      expect(result).not.toBeNull();
      // Should pick the best scoring node among Standard+ (b and c)
      expect(result!.node.reputationScore).toBeGreaterThanOrEqual(50);
    });

    it("should respect minReputationTier filter - Elite", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 90 }), // Verified
        makeNode({ deviceId: "b", reputationScore: 96 }), // Elite
      ];
      const result = routeMarketplaceRequest(nodes, {
        model: "qwen3:14b",
        minReputationTier: "Elite",
      });
      expect(result).not.toBeNull();
      expect(result!.node.deviceId).toBe("b");
    });

    it("should return null when no node meets minReputationTier", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 70 }), // Standard
      ];
      const result = routeMarketplaceRequest(nodes, {
        model: "qwen3:14b",
        minReputationTier: "Elite",
      });
      expect(result).toBeNull();
    });
  });

  describe("filterByReputationTier", () => {
    it("should filter by Standard tier (>= 50)", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 30 }),
        makeNode({ deviceId: "b", reputationScore: 55 }),
        makeNode({ deviceId: "c", reputationScore: 90 }),
      ];
      const result = filterByReputationTier(nodes, "Standard");
      expect(result).toHaveLength(2);
      expect(result.map((n) => n.deviceId)).toEqual(
        expect.arrayContaining(["b", "c"]),
      );
    });

    it("should filter by Verified tier (>= 80)", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 60 }),
        makeNode({ deviceId: "b", reputationScore: 85 }),
      ];
      const result = filterByReputationTier(nodes, "Verified");
      expect(result).toHaveLength(1);
      expect(result[0].deviceId).toBe("b");
    });

    it("should filter by Elite tier (>= 95)", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 94 }),
        makeNode({ deviceId: "b", reputationScore: 96 }),
      ];
      const result = filterByReputationTier(nodes, "Elite");
      expect(result).toHaveLength(1);
      expect(result[0].deviceId).toBe("b");
    });

    it("should return all for Unverified tier (>= 0)", () => {
      const nodes = [
        makeNode({ deviceId: "a", reputationScore: 10 }),
        makeNode({ deviceId: "b", reputationScore: 90 }),
      ];
      const result = filterByReputationTier(nodes, "Unverified");
      expect(result).toHaveLength(2);
    });

    it("should return all when no tier specified", () => {
      const nodes = [
        makeNode({ reputationScore: 10 }),
        makeNode({ reputationScore: 90 }),
      ];
      const result = filterByReputationTier(nodes);
      expect(result).toHaveLength(2);
    });

    it("should include node at exact tier boundary", () => {
      const nodes = [makeNode({ reputationScore: 50 })];
      const result = filterByReputationTier(nodes, "Standard");
      expect(result).toHaveLength(1);
    });
  });

  describe("TIER_MIN_SCORES", () => {
    it("should have correct thresholds", () => {
      expect(TIER_MIN_SCORES.Unverified).toBe(0);
      expect(TIER_MIN_SCORES.Standard).toBe(50);
      expect(TIER_MIN_SCORES.Verified).toBe(80);
      expect(TIER_MIN_SCORES.Elite).toBe(95);
    });
  });
});
