import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { MemoryRouter, Route, Routes } from "react-router-dom";
import { afterEach, describe, expect, it, vi } from "vitest";
import AuditLogsPage from "../audit/AuditLogsPage";
import ClientConsentsPage from "../clients/ClientConsentsPage";
import ClientDetailsPage from "../clients/ClientDetailsPage";
import ClientGeneralPage from "../clients/ClientGeneralPage";
import ClientRelationsPage from "../clients/ClientRelationsPage";
import ClientsPage from "../clients/ClientsPage";
import { ClientFederationPage } from "../clients/routes/ClientFederationPage";
import DeveloperRequestPage from "../developer-request/DeveloperRequestPage";
import GlobalOverviewPage from "../overview/GlobalOverviewPage";
import ProfilePage from "../profile/ProfilePage";
import {
  approveDeveloperRequest,
  cancelDeveloperRequestApproval,
  rejectDeveloperRequest,
} from "../../lib/devApi";

const authProfile = {
  sub: "user-1",
  role: "super_admin",
  tenant_id: "tenant-1",
  companyCode: "HANMAC",
};

vi.mock("react-oidc-context", () => ({
  useAuth: () => ({
    isAuthenticated: true,
    isLoading: false,
    user: {
      access_token: "access-token",
      profile: authProfile,
    },
    signinRedirect: vi.fn(),
    removeUser: vi.fn(),
  }),
}));

vi.mock("../../lib/i18n", () => ({
  t: (key: string, fallback?: string, vars?: Record<string, unknown>) => {
    let text = fallback ?? key;
    for (const [name, value] of Object.entries(vars ?? {})) {
      text = text.replaceAll(`{{${name}}}`, String(value));
    }
    return text;
  },
}));

vi.mock("../../../../common/core/components/audit", () => ({
  AuditLogTable: ({
    logs,
  }: {
    logs: Array<{ user_id: string; event_type: string }>;
  }) => (
    <div>
      {logs.map((log) => (
        <div key={`${log.user_id}-${log.event_type}`}>
          <span>{log.user_id}</span>
          <span>{log.event_type}</span>
        </div>
      ))}
    </div>
  ),
}));

const clientSummary = {
  id: "client-a",
  name: "Console App",
  type: "private" as const,
  status: "active" as const,
  createdAt: "2026-05-01T00:00:00Z",
  redirectUris: ["https://app.example/callback"],
  scopes: ["openid", "profile"],
  tokenEndpointAuthMethod: "client_secret_basic",
  metadata: {
    headless_login_enabled: true,
    headless_login_jwks_uri: "https://app.example/jwks.json",
    id_token_claims: [
      {
        namespace: "rp_claims",
        key: "employee_id",
        value: "E001",
        valueType: "text",
        readPermission: "admin_only",
        writePermission: "admin_only",
      },
    ],
  },
};

const clientDetail = {
  client: {
    ...clientSummary,
    clientSecret: "secret-value",
    jwksUri: "https://app.example/jwks.json",
    backchannelLogoutUri: "https://app.example/logout",
    backchannelLogoutSessionRequired: true,
    grantTypes: ["authorization_code"],
    responseTypes: ["code"],
  },
  endpoints: {
    discovery: "https://sso.example/.well-known/openid-configuration",
    issuer: "https://sso.example",
    authorization: "https://sso.example/oauth2/auth",
    token: "https://sso.example/oauth2/token",
    userinfo: "https://sso.example/userinfo",
  },
  headlessJwksCache: {
    clientId: "client-a",
    jwksUri: "https://app.example/jwks.json",
    cachedAt: "2026-05-01T00:00:00Z",
    expiresAt: "2026-05-02T00:00:00Z",
    lastCheckedAt: "2026-05-01T01:00:00Z",
    lastSuccessfulVerificationAt: "2026-05-01T01:00:00Z",
    lastRefreshStatus: "success" as const,
    cachedKids: ["kid-1"],
    parsedKeys: [{ kid: "kid-1", kty: "RSA", use: "sig", alg: "RS256" }],
  },
};

vi.mock("../../lib/devApi", () => ({
  fetchClients: vi.fn(async () => ({
    items: [clientSummary],
    limit: 100,
    offset: 0,
  })),
  fetchDevStats: vi.fn(async () => ({
    total_clients: 1,
    active_sessions: 12,
    auth_failures_24h: 2,
  })),
  fetchDevRPUsageDaily: vi.fn(async () => ({
    days: 14,
    period: "day",
    items: [
      {
        date: "2026-05-01",
        tenantId: "tenant-1",
        tenantType: "COMPANY",
        tenantName: "Hanmac",
        clientId: "client-a",
        clientName: "Console App",
        loginRequests: 10,
        otherRequests: 4,
        uniqueSubjects: 3,
      },
      {
        date: "2026-05-08",
        tenantId: "tenant-1",
        tenantType: "COMPANY",
        tenantName: "Hanmac",
        clientId: "client-a",
        clientName: "Console App",
        loginRequests: 8,
        otherRequests: 5,
        uniqueSubjects: 4,
      },
    ],
  })),
  fetchClient: vi.fn(async () => clientDetail),
  fetchClientRelations: vi.fn(async () => ({
    items: [
      {
        relation: "admins",
        subject: "User:user-1",
        subjectType: "User",
        subjectId: "user-1",
        userName: "Dev Admin",
        userEmail: "dev@example.com",
      },
    ],
  })),
  fetchRPUserMetadata: vi.fn(async () => ({
    clientId: "client-a",
    userId: "user-1",
    metadata: {
      approvalLevel: "A",
    },
  })),
  updateRPUserMetadata: vi.fn(async (_clientId: string, userId: string, metadata: Record<string, unknown>) => ({
    clientId: "client-a",
    userId,
    metadata,
  })),
  fetchDevUsers: vi.fn(async () => ({
    items: [
      {
        id: "user-2",
        name: "Editor User",
        email: "editor@example.com",
        loginId: "editor",
      },
    ],
  })),
  addClientRelation: vi.fn(async () => ({
    relation: "admins",
    subject: "User:user-2",
    subjectType: "User",
    subjectId: "user-2",
  })),
  removeClientRelation: vi.fn(async () => undefined),
  updateClientStatus: vi.fn(async () => clientDetail),
  createClient: vi.fn(async () => clientDetail),
  updateClient: vi.fn(async () => clientDetail),
  rotateClientSecret: vi.fn(async () => clientDetail),
  refreshHeadlessJwksCache: vi.fn(async () => clientDetail),
  revokeHeadlessJwksCache: vi.fn(async () => undefined),
  deleteClient: vi.fn(async () => undefined),
  fetchConsents: vi.fn(async () => ({
    items: [
      {
        subject: "user-1",
        userName: "Consent User",
        clientId: "client-a",
        clientName: "Console App",
        grantedScopes: ["openid", "profile"],
        authenticatedAt: "2026-05-01T02:00:00Z",
        createdAt: "2026-05-01T00:00:00Z",
        status: "active",
        tenantId: "tenant-1",
        tenantName: "Hanmac",
        rpMetadata: {
          approvalLevel: "A",
          reviewedAt: "2026-06-09T09:30:00+09:00",
        },
      },
    ],
  })),
  revokeConsent: vi.fn(async () => undefined),
  listIdpConfigsForClient: vi.fn(async () => [
    {
      id: "idp-1",
      client_id: "client-a",
      provider_type: "oidc",
      display_name: "Workspace OIDC",
      status: "active",
      issuer_url: "https://accounts.example",
      oidc_client_id: "oidc-client",
      scopes: "openid email profile",
      createdAt: "2026-05-01T00:00:00Z",
      updatedAt: "2026-05-01T00:00:00Z",
    },
  ]),
  createIdpConfigForClient: vi.fn(async (payload) => ({
    id: "idp-1",
    ...payload,
    status: "active",
    createdAt: "2026-05-01T00:00:00Z",
    updatedAt: "2026-05-01T00:00:00Z",
  })),
  updateIdpConfig: vi.fn(async (_clientId, idpId, payload) => ({
    id: idpId,
    client_id: "client-a",
    provider_type: "oidc",
    display_name: "Provider",
    status: "active",
    createdAt: "2026-05-01T00:00:00Z",
    updatedAt: "2026-05-01T00:00:00Z",
    ...payload,
  })),
  deleteIdpConfig: vi.fn(async () => undefined),
  fetchDevAuditLogs: vi.fn(async () => ({
    items: [
      {
        event_id: "event-1",
        timestamp: "2026-05-01T00:00:00Z",
        user_id: "user-1",
        event_type: "client.update",
        status: "success",
        ip_address: "127.0.0.1",
        user_agent: "Vitest",
        details: "{\"client_id\":\"client-a\"}",
      },
    ],
    limit: 50,
  })),
  fetchMyTenants: vi.fn(async () => [
    {
      id: "tenant-1",
      name: "Hanmac",
      slug: "hanmac",
      type: "COMPANY",
      status: "active",
      description: "",
      memberCount: 10,
      createdAt: "2026-05-01T00:00:00Z",
      updatedAt: "2026-05-01T00:00:00Z",
    },
  ]),
  fetchDeveloperRequestStatus: vi.fn(async () => ({ status: "approved" })),
  requestDeveloperAccess: vi.fn(async () => ({ status: "pending" })),
  fetchDeveloperRequests: vi.fn(async () => [
    {
      id: 1,
      userId: "user-3",
      tenantId: "tenant-1",
      name: "Requester",
      organization: "Hanmac",
      email: "requester@example.com",
      reason: "Need RP access",
      status: "pending",
      createdAt: "2026-05-01T00:00:00Z",
      updatedAt: "2026-05-01T00:00:00Z",
    },
    {
      id: 2,
      userId: "user-4",
      tenantId: "tenant-1",
      name: "Approved Requester",
      organization: "Hanmac",
      email: "approved@example.com",
      reason: "Need elevated access",
      status: "approved",
      adminNotes: "Reviewed and approved",
      createdAt: "2026-05-02T00:00:00Z",
      updatedAt: "2026-05-02T00:00:00Z",
    },
  ]),
  approveDeveloperRequest: vi.fn(async () => ({ status: "approved" })),
  rejectDeveloperRequest: vi.fn(async () => ({ status: "rejected" })),
  cancelDeveloperRequestApproval: vi.fn(async () => ({ status: "cancelled" })),
}));

vi.mock("../auth/authApi", () => ({
  fetchMe: vi.fn(async () => ({
    id: "user-1",
    email: "dev@example.com",
    name: "Dev Admin",
    role: "super_admin",
    tenantId: "tenant-1",
  })),
}));

const roots: Root[] = [];

afterEach(() => {
  for (const root of roots.splice(0)) {
    act(() => {
      root.unmount();
    });
  }
});

async function renderPage(
  element: React.ReactElement,
  {
    path = "/",
    entry = path,
  }: {
    path?: string;
    entry?: string;
  } = {},
) {
  const container = document.createElement("div");
  document.body.appendChild(container);
  const root = createRoot(container);
  roots.push(root);
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  await act(async () => {
    root.render(
      <QueryClientProvider client={queryClient}>
        <MemoryRouter initialEntries={[entry]}>
          <Routes>
            <Route path={path} element={element} />
          </Routes>
        </MemoryRouter>
      </QueryClientProvider>,
    );
  });

  await act(async () => {
    await new Promise((resolve) => setTimeout(resolve, 0));
  });

  return container;
}

async function setInputValue(input: HTMLInputElement, value: string) {
  const descriptor = Object.getOwnPropertyDescriptor(
    HTMLInputElement.prototype,
    "value",
  );
  descriptor?.set?.call(input, value);
  input.dispatchEvent(new Event("input", { bubbles: true }));
  await new Promise((resolve) => setTimeout(resolve, 0));
}

describe("devfront coverage smoke pages", () => {
  it("renders overview, client list, audit, developer request, and profile pages", async () => {
    const overview = await renderPage(<GlobalOverviewPage />);
    expect(overview.textContent).toContain("Console App");

    const clients = await renderPage(<ClientsPage />);
    expect(clients.textContent).toContain("Console App");

    const audit = await renderPage(<AuditLogsPage />);
    expect(audit.textContent).toContain("client.update");

    const requests = await renderPage(<DeveloperRequestPage />);
    expect(requests.textContent).toContain("Requester");

    const profile = await renderPage(<ProfilePage />);
    expect(profile.textContent).toContain("Dev Admin");
  });

  it("renders client detail, settings, consent, federation, and relationship pages", async () => {
    const details = await renderPage(<ClientDetailsPage />, {
      path: "/clients/:id",
      entry: "/clients/client-a",
    });
    expect(details.textContent).toContain("Console App");

    const settings = await renderPage(<ClientGeneralPage />, {
      path: "/clients/:id/settings",
      entry: "/clients/client-a/settings",
    });
    expect(settings.textContent).toContain("Console App");
    expect(settings.textContent).not.toContain("top-level");
    expect(settings.textContent).toContain("Date");
    expect(settings.textContent).toContain("Datetime");
    expect(settings.textContent).toContain("관리자만 가능");

    const consents = await renderPage(<ClientConsentsPage />, {
      path: "/clients/:id/consents",
      entry: "/clients/client-a/consents",
    });
    expect(consents.textContent).toContain("Consent User");
    expect(consents.textContent).toContain("approvalLevel");
    expect(consents.textContent).toContain("A");

    const federation = await renderPage(<ClientFederationPage />, {
      path: "/clients/:id/federation",
      entry: "/clients/client-a/federation",
    });
    expect(federation.textContent).toContain("Workspace OIDC");

    const relations = await renderPage(<ClientRelationsPage />, {
      path: "/clients/:id/relationships",
      entry: "/clients/client-a/relationships",
    });
    expect(relations.textContent).toContain("Dev Admin");
  });

  it("covers developer request actions", async () => {
    const alertSpy = vi.spyOn(window, "alert").mockImplementation(() => undefined);
    const requests = await renderPage(<DeveloperRequestPage />);

    expect(requests.textContent).toContain("Requester");
    expect(requests.textContent).toContain("Approved Requester");

    const pendingNote = Array.from(
      requests.querySelectorAll("input"),
    ).find((input) => input.getAttribute("placeholder") === "메모 입력 (선택)...") as HTMLInputElement | undefined;
    const cancelNote = Array.from(
      requests.querySelectorAll("input"),
    ).find(
      (input) => input.getAttribute("placeholder") === "승인 취소 사유 입력...",
    ) as HTMLInputElement | undefined;

    expect(pendingNote).toBeTruthy();
    expect(cancelNote).toBeTruthy();

    await act(async () => {
      await setInputValue(pendingNote!, "");
    });

    const buttons = Array.from(requests.querySelectorAll("button"));
    const rejectButton = buttons.find((button) => button.textContent === "반려");
    const approveButton = buttons.find((button) => button.textContent === "승인");
    const cancelButton = buttons.find(
      (button) => button.textContent === "승인 취소",
    );

    expect(rejectButton).toBeTruthy();
    expect(approveButton).toBeTruthy();
    expect(cancelButton).toBeTruthy();

    await act(async () => {
      rejectButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
    });
    expect(alertSpy).toHaveBeenCalledWith("반려 사유를 입력해주세요.");

    await act(async () => {
      await setInputValue(pendingNote!, "Need more context");
      approveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
      await new Promise((resolve) => setTimeout(resolve, 0));
    });

    await act(async () => {
      await setInputValue(cancelNote!, "Approve needs revision");
      cancelButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
      await new Promise((resolve) => setTimeout(resolve, 0));
    });

    expect(approveDeveloperRequest).toHaveBeenCalledWith(1, "Need more context");
    expect(rejectDeveloperRequest).not.toHaveBeenCalled();
    expect(cancelDeveloperRequestApproval).toHaveBeenCalledWith(
      2,
      "Approve needs revision",
    );

    alertSpy.mockRestore();
  });
});
