import { beforeEach, describe, expect, it, vi } from "vitest";

import { generateElderAiAnalysis, listElderAiAnalyses } from "@/modules/ai/server/analysis";
import {
  createKnowledgeEntry,
  deleteKnowledgeEntry,
  listKnowledgeEntries,
  updateKnowledgeEntry,
} from "@/modules/ai/server/knowledge";
import type { ElderAiAnalysisHistoryItem, KnowledgeEntry, KnowledgeEntryInput } from "@/modules/ai/types";
import { recordAuditLog } from "@/modules/core/server/audit";
import { requirePermission } from "@/modules/core/server/auth";
import type { AuthContext } from "@/modules/core/types";

vi.mock("@/modules/core/server/auth", () => ({
  requirePermission: vi.fn(),
}));

vi.mock("@/modules/core/server/audit", () => ({
  recordAuditLog: vi.fn(),
}));

vi.mock("@/modules/ai/server/analysis", () => ({
  generateElderAiAnalysis: vi.fn(),
  listElderAiAnalyses: vi.fn(),
}));

vi.mock("@/modules/ai/server/knowledge", () => ({
  createKnowledgeEntry: vi.fn(),
  deleteKnowledgeEntry: vi.fn(),
  listKnowledgeEntries: vi.fn(),
  updateKnowledgeEntry: vi.fn(),
}));

const account: AuthContext["account"] = {
  id: "account-1",
  name: "AI Admin",
  email: "ai-admin@example.com",
  avatarUrl: "",
  role: "org_admin",
  status: "active",
  createdAt: "2026-07-02T00:00:00.000Z",
  updatedAt: "2026-07-02T00:00:00.000Z",
};

const organization: NonNullable<AuthContext["organization"]> = {
  id: "org-1",
  name: "TeaCare Home",
  slug: "teacare",
  status: "active",
  registrationEnabled: true,
  oidcEnabled: false,
  oidcIssuerUrl: "",
  oidcClientId: "",
  oidcHasClientSecret: false,
  oidcScopes: "",
  oidcRedirectUri: "",
  oidcAvatarClaim: "",
  oidcAutoProvision: false,
  createdAt: "2026-07-02T00:00:00.000Z",
  updatedAt: "2026-07-02T00:00:00.000Z",
};

function createAuthContext(): AuthContext {
  return {
    account,
    organization,
    organizations: [
      {
        id: organization.id,
        name: organization.name,
        slug: organization.slug,
        status: organization.status,
        registrationEnabled: organization.registrationEnabled,
        oidcEnabled: organization.oidcEnabled,
        roleLabel: "Admin",
        isActive: true,
      },
    ],
    membership: {
      id: "membership-1",
      accountId: account.id,
      organizationId: organization.id,
      roleId: "role-1",
      roleKey: "org_admin",
      roleLabel: "Admin",
      status: "active",
      createdAt: "2026-07-02T00:00:00.000Z",
      updatedAt: "2026-07-02T00:00:00.000Z",
    },
    permissions: ["ai:read", "elder:read", "knowledge:read", "knowledge:manage"],
    session: {
      id: "session-1",
      accountId: account.id,
      activeOrganizationId: organization.id,
      createdAt: "2026-07-02T00:00:00.000Z",
      expiresAt: "2026-07-09T00:00:00.000Z",
    },
  };
}

const analysis: ElderAiAnalysisHistoryItem = {
  id: "analysis-1",
  elderId: "elder-1",
  status: "completed",
  dataScopes: ["elder"],
  createdAt: "2026-07-02T08:00:00.000Z",
  restricted: false,
  result: {
    overallRiskLevel: "medium",
    summary: "Resident has elevated fall risk at night.",
    keyFindings: [
      {
        category: "fall-risk",
        severity: "warning",
        evidence: "Night wandering and prior fall history are present.",
        citationIds: ["resident-1"],
      },
    ],
    recommendations: [
      {
        title: "Increase night checks",
        priority: "high",
        rationale: "Additional observation reduces missed night wandering events.",
        suggestedNextStep: "Add a night-shift round note for the next care review.",
        citationIds: ["resident-1"],
      },
    ],
    dataGaps: [],
    citations: [
      {
        id: "resident-1",
        sourceType: "resident_context",
        sourceId: "elder-1",
        title: "Resident risk notes",
        excerpt: "Night wandering and prior fall history.",
      },
    ],
    confidence: 0.74,
    modelSummary: {
      provider: "openai-compatible",
      chatModel: "gpt-test",
      embeddingModel: "embedding-test",
    },
  },
};

const knowledgeInput: KnowledgeEntryInput = {
  scope: "organization",
  organizationId: "org-1",
  title: "Fall prevention",
  category: "Safety",
  tags: "fall,night",
  body: "Keep walkways clear and increase night rounds after wandering events.",
  status: "enabled",
};

const knowledgeEntry: KnowledgeEntry = {
  ...knowledgeInput,
  id: "knowledge-1",
  createdAt: "2026-07-02T08:00:00.000Z",
  updatedAt: "2026-07-02T08:00:00.000Z",
};

function allowAuth(): void {
  vi.mocked(requirePermission).mockResolvedValue({ success: true, context: createAuthContext() });
}

function deniedAuth(reason = "权限不足", status = 403): { success: false; response: Response } {
  return {
    success: false,
    response: Response.json({ success: false, reason }, { status }),
  };
}

function jsonRequest(body: Record<string, unknown>): Request {
  return new Request("http://localhost/api/ai/knowledge", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body),
  });
}

async function readJson(response: Response): Promise<Record<string, unknown>> {
  return (await response.json()) as Record<string, unknown>;
}

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

describe("AI API routes", () => {
  it("requires ai:read before elder analysis services are reached", async () => {
    vi.mocked(requirePermission).mockResolvedValue(deniedAuth());

    const { GET } = await import("./elders/[id]/analyses/route");
    const response = await GET(new Request("http://localhost/api/ai/elders/elder-1/analyses"), {
      params: Promise.resolve({ id: "elder-1" }),
    });
    const body = await readJson(response);

    expect(response.status).toBe(403);
    expect(body).toEqual({ success: false, reason: "权限不足" });
    expect(requirePermission).toHaveBeenCalledTimes(1);
    expect(requirePermission).toHaveBeenCalledWith("ai:read", expect.objectContaining({ targetId: "elder-1" }));
    expect(listElderAiAnalyses).not.toHaveBeenCalled();
  });

  it("requires elder:read after ai:read before listing elder analysis history", async () => {
    vi.mocked(requirePermission)
      .mockResolvedValueOnce({ success: true, context: createAuthContext() })
      .mockResolvedValueOnce(deniedAuth("权限不足", 403));

    const { GET } = await import("./elders/[id]/analyses/route");
    const response = await GET(new Request("http://localhost/api/ai/elders/elder-1/analyses"), {
      params: Promise.resolve({ id: "elder-1" }),
    });
    const body = await readJson(response);

    expect(response.status).toBe(403);
    expect(body.reason).toBe("权限不足");
    expect(requirePermission).toHaveBeenNthCalledWith(2, "elder:read", expect.objectContaining({
      organizationId: "org-1",
      targetId: "elder-1",
    }));
    expect(listElderAiAnalyses).not.toHaveBeenCalled();
  });

  it("propagates analysis service failures with the service status", async () => {
    vi.mocked(generateElderAiAnalysis).mockResolvedValue({ success: false, reason: "AI_API_KEY 未配置", status: 500 });

    const { POST } = await import("./elders/[id]/analyses/route");
    const response = await POST(new Request("http://localhost/api/ai/elders/elder-1/analyses", { method: "POST" }), {
      params: Promise.resolve({ id: "elder-1" }),
    });
    const body = await readJson(response);

    expect(response.status).toBe(500);
    expect(body).toEqual({ success: false, reason: "AI_API_KEY 未配置" });
    expect(generateElderAiAnalysis).toHaveBeenCalledWith(createAuthContext(), "elder-1");
  });

  it("returns structured success for generated analysis", async () => {
    vi.mocked(generateElderAiAnalysis).mockResolvedValue({ success: true, data: { analysis } });

    const { POST } = await import("./elders/[id]/analyses/route");
    const response = await POST(new Request("http://localhost/api/ai/elders/elder-1/analyses", { method: "POST" }), {
      params: Promise.resolve({ id: "elder-1" }),
    });
    const body = await readJson(response);

    expect(response.status).toBe(201);
    expect(body).toEqual({ success: true, reason: "AI 分析已生成", analysis });
  });

  it("requires knowledge:read before listing knowledge entries", async () => {
    vi.mocked(requirePermission).mockResolvedValue(deniedAuth());

    const { GET } = await import("./knowledge/route");
    const response = await GET();
    const body = await readJson(response);

    expect(response.status).toBe(403);
    expect(body.reason).toBe("权限不足");
    expect(requirePermission).toHaveBeenCalledWith("knowledge:read", expect.objectContaining({ action: "ai.knowledge.list" }));
    expect(listKnowledgeEntries).not.toHaveBeenCalled();
  });

  it("requires knowledge:manage before creating knowledge entries", async () => {
    vi.mocked(requirePermission).mockResolvedValue(deniedAuth());

    const { POST } = await import("./knowledge/route");
    const response = await POST(jsonRequest({ ...knowledgeInput }));
    const body = await readJson(response);

    expect(response.status).toBe(403);
    expect(body.reason).toBe("权限不足");
    expect(requirePermission).toHaveBeenCalledWith("knowledge:manage", expect.objectContaining({ action: "ai.knowledge.create" }));
    expect(createKnowledgeEntry).not.toHaveBeenCalled();
    expect(recordAuditLog).not.toHaveBeenCalled();
  });

  it("rejects invalid knowledge bodies before calling the service or audit log", async () => {
    const { POST } = await import("./knowledge/route");
    const response = await POST(jsonRequest({ ...knowledgeInput, title: "   " }));
    const body = await readJson(response);

    expect(response.status).toBe(400);
    expect(body).toEqual({ success: false, reason: "知识标题不能为空" });
    expect(createKnowledgeEntry).not.toHaveBeenCalled();
    expect(recordAuditLog).not.toHaveBeenCalled();
  });

  it("propagates knowledge service failures with the service status", async () => {
    vi.mocked(createKnowledgeEntry).mockResolvedValue({ success: false, reason: "知识库向量生成失败", status: 500 });

    const { POST } = await import("./knowledge/route");
    const response = await POST(jsonRequest({ ...knowledgeInput }));
    const body = await readJson(response);

    expect(response.status).toBe(500);
    expect(body).toEqual({ success: false, reason: "知识库向量生成失败" });
    expect(recordAuditLog).not.toHaveBeenCalled();
  });

  it("returns created knowledge entries and records create audit success", async () => {
    vi.mocked(createKnowledgeEntry).mockResolvedValue({ success: true, data: { entry: knowledgeEntry } });

    const { POST } = await import("./knowledge/route");
    const response = await POST(jsonRequest({ ...knowledgeInput }));
    const body = await readJson(response);

    expect(response.status).toBe(201);
    expect(body).toEqual({ success: true, reason: "知识库条目已创建", entry: knowledgeEntry });
    expect(recordAuditLog).toHaveBeenCalledWith(expect.objectContaining({
      actor: account,
      organizationId: "org-1",
      action: "ai.knowledge.create",
      targetType: "ai_knowledge",
      targetId: "knowledge-1",
      result: "success",
    }));
  });

  it("records update and delete audit success only after knowledge mutations succeed", async () => {
    vi.mocked(updateKnowledgeEntry).mockResolvedValue({ success: true, data: { entry: knowledgeEntry } });
    vi.mocked(deleteKnowledgeEntry).mockResolvedValue({ success: true, data: { entry: knowledgeEntry } });

    const { PATCH, DELETE } = await import("./knowledge/[id]/route");
    const patchResponse = await PATCH(jsonRequest({ ...knowledgeInput }), {
      params: Promise.resolve({ id: "knowledge-1" }),
    });
    const deleteResponse = await DELETE(new Request("http://localhost/api/ai/knowledge/knowledge-1", { method: "DELETE" }), {
      params: Promise.resolve({ id: "knowledge-1" }),
    });

    expect(patchResponse.status).toBe(200);
    expect(deleteResponse.status).toBe(200);
    expect(recordAuditLog).toHaveBeenCalledWith(expect.objectContaining({ action: "ai.knowledge.update", result: "success" }));
    expect(recordAuditLog).toHaveBeenCalledWith(expect.objectContaining({ action: "ai.knowledge.delete", result: "success" }));
  });
});
