import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";

import { and, eq, gt, gte, isNull, lt, sql } from "drizzle-orm";
import { cookies } from "next/headers";

import { jsonFailure } from "@/modules/core/server/api";
import { recordAuditLog } from "@/modules/core/server/audit";
import { seedDefaultWorkspaceData } from "@/modules/core/server/default-workspace-data";
import { getDatabase } from "@/modules/core/server/db";
import { getPermissionsForRole, hasPermission, seedOrganizationRoles } from "@/modules/core/server/permissions";
import {
  accounts,
  joinRequests,
  memberships,
  organizationInvitations,
  organizations,
  roles,
  sessions,
} from "@/modules/core/server/schema";
import { getSystemSettings } from "@/modules/core/server/system-settings";
import type {
  Account,
  AccountOrganizationOption,
  AuthContext,
  Membership,
  Organization,
  Permission,
  PublicAccount,
  Session,
} from "@/modules/core/types";
import { RESERVED_WORKSPACE_SLUGS } from "@/modules/shared/lib/workspace-routing";

const SESSION_COOKIE = "teatea_session";
const SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 7;

type AccountRow = typeof accounts.$inferSelect;
type OrganizationRow = typeof organizations.$inferSelect;
type MembershipRow = typeof memberships.$inferSelect;
type RoleRow = typeof roles.$inferSelect;
type SessionRow = typeof sessions.$inferSelect;

export function normalizeEmail(email: string): string {
  return email.trim().toLowerCase();
}

function toIsoString(value: Date): string {
  return value.toISOString();
}

function toOrganization(row: OrganizationRow): Organization {
  return {
    id: row.id,
    name: row.name,
    slug: row.slug,
    status: row.status,
    registrationEnabled: row.registrationEnabled,
    oidcEnabled: row.oidcEnabled,
    oidcIssuerUrl: row.oidcIssuerUrl,
    oidcClientId: row.oidcClientId,
    oidcHasClientSecret: row.oidcClientSecret.length > 0,
    oidcScopes: row.oidcScopes,
    oidcRedirectUri: row.oidcRedirectUri,
    oidcAvatarClaim: row.oidcAvatarClaim,
    oidcAutoProvision: row.oidcAutoProvision,
    createdAt: toIsoString(row.createdAt),
    updatedAt: toIsoString(row.updatedAt),
  };
}

function toSession(row: SessionRow): Session {
  return {
    id: row.id,
    accountId: row.accountId,
    activeOrganizationId: row.activeOrganizationId ?? undefined,
    createdAt: toIsoString(row.createdAt),
    expiresAt: toIsoString(row.expiresAt),
  };
}

function toMembership(row: MembershipRow, role: RoleRow): Membership {
  return {
    id: row.id,
    accountId: row.accountId,
    organizationId: row.organizationId,
    roleId: row.roleId,
    roleKey: role.key,
    roleLabel: role.label,
    status: row.status,
    createdAt: toIsoString(row.createdAt),
    updatedAt: toIsoString(row.updatedAt),
  };
}

function toOrganizationOption(
  organization: OrganizationRow,
  roleLabel: string,
  activeOrganizationId: string | null,
): AccountOrganizationOption {
  return {
    id: organization.id,
    name: organization.name,
    slug: organization.slug,
    status: organization.status,
    registrationEnabled: organization.registrationEnabled,
    oidcEnabled: organization.oidcEnabled,
    roleLabel,
    isActive: organization.id === activeOrganizationId,
  };
}

export function toPublicAccount(account: AccountRow | Account, roleKey?: string, organization?: Organization): PublicAccount {
  const fallbackRole = "role" in account ? account.role : "viewer";
  const fallbackOrganization = "organization" in account ? account.organization : undefined;
  const fallbackOrganizationId = "organizationId" in account ? account.organizationId : undefined;

  return {
    id: account.id,
    platformRoleId: "platformRoleId" in account ? account.platformRoleId ?? undefined : undefined,
    name: account.name,
    email: account.email,
    avatarUrl: "avatarUrl" in account ? account.avatarUrl : "",
    role: roleKey ?? fallbackRole,
    organization: organization?.name ?? fallbackOrganization,
    organizationId: organization?.id ?? fallbackOrganizationId,
    status: account.status,
    createdAt: typeof account.createdAt === "string" ? account.createdAt : toIsoString(account.createdAt),
    updatedAt: typeof account.updatedAt === "string" ? account.updatedAt : toIsoString(account.updatedAt),
  };
}

export function hashPassword(password: string, salt = randomBytes(16).toString("hex")): {
  hash: string;
  salt: string;
} {
  return {
    hash: scryptSync(password, salt, 64).toString("hex"),
    salt,
  };
}

export function verifyPassword(password: string, account: Pick<AccountRow, "passwordHash" | "passwordSalt">): boolean {
  const candidate = Buffer.from(hashPassword(password, account.passwordSalt).hash, "hex");
  const stored = Buffer.from(account.passwordHash, "hex");

  if (candidate.length !== stored.length) {
    return false;
  }

  return timingSafeEqual(candidate, stored);
}

function createSlug(name: string): string {
  const base = name
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-|-$/g, "");

  const slug = base.length > 0 ? base : `org-${Date.now()}`;
  return RESERVED_WORKSPACE_SLUGS.has(slug) ? `org-${slug}` : slug;
}

export async function setSessionCookie(sessionId: string): Promise<void> {
  const cookieStore = await cookies();
  cookieStore.set({
    name: SESSION_COOKIE,
    value: sessionId,
    httpOnly: true,
    sameSite: "lax",
    path: "/",
    maxAge: SESSION_MAX_AGE_SECONDS,
  });
}

export async function clearSessionCookie(): Promise<void> {
  const cookieStore = await cookies();
  cookieStore.delete(SESSION_COOKIE);
}

export async function getBootstrapState(): Promise<{ setupRequired: boolean }> {
  const database = getDatabase();
  const existingAccounts = await database.select({ id: accounts.id }).from(accounts).limit(1);
  return { setupRequired: existingAccounts.length === 0 };
}

export async function setupFirstPlatformAdmin(input: {
  organization: string;
  name: string;
  email: string;
  password: string;
}): Promise<{ account: PublicAccount; session: Session }> {
  const database = getDatabase();
  const password = hashPassword(input.password);
  const expiresAt = new Date(Date.now() + SESSION_MAX_AGE_SECONDS * 1000);

  const created = await database.transaction(async (transaction) => {
    const existingAccounts = await transaction.select({ id: accounts.id }).from(accounts).limit(1);
    if (existingAccounts.length > 0) {
      return null;
    }

    const platformRoleRows = await transaction
      .select()
      .from(roles)
      .where(and(eq(roles.key, "platform_admin"), isNull(roles.organizationId)))
      .limit(1);
    const platformRole = platformRoleRows[0];
    if (!platformRole) {
      throw new Error("平台超管角色未初始化，请先运行数据库初始化");
    }

    const organizationRows = await transaction
      .insert(organizations)
      .values({
        name: input.organization.trim(),
        slug: createSlug(input.organization),
      })
      .returning();
    const organization = organizationRows[0];
    if (!organization) {
      throw new Error("机构初始化失败");
    }

    const accountRows = await transaction
      .insert(accounts)
      .values({
        platformRoleId: platformRole.id,
        name: input.name.trim(),
        email: normalizeEmail(input.email),
        passwordHash: password.hash,
        passwordSalt: password.salt,
        status: "active",
      })
      .returning();
    const account = accountRows[0];
    if (!account) {
      throw new Error("账号初始化失败");
    }

    const sessionRows = await transaction
      .insert(sessions)
      .values({
        accountId: account.id,
        activeOrganizationId: organization.id,
        expiresAt,
      })
      .returning();
    const session = sessionRows[0];
    if (!session) {
      throw new Error("会话初始化失败");
    }

    return { account, organization, platformRole, session };
  });

  if (!created) {
    throw new Error("系统已完成初始化");
  }

  await seedOrganizationRoles(created.organization.id);
  const databaseAfterSeed = getDatabase();
  const orgAdminRows = await databaseAfterSeed
    .select()
    .from(roles)
    .where(and(eq(roles.organizationId, created.organization.id), eq(roles.key, "org_admin")))
    .limit(1);
  const orgAdminRole = orgAdminRows[0];
  if (orgAdminRole) {
    await databaseAfterSeed
      .insert(memberships)
      .values({
        accountId: created.account.id,
        organizationId: created.organization.id,
        roleId: orgAdminRole.id,
        status: "active",
      })
      .onConflictDoNothing();
  }
  await seedDefaultWorkspaceData(created.organization.id);

  const organization = toOrganization(created.organization);
  const publicAccount = toPublicAccount(created.account, created.platformRole.key, organization);
  const session = toSession(created.session);

  await recordAuditLog({
    actor: publicAccount,
    organizationId: organization.id,
    action: "account.setup",
    targetType: "account",
    targetId: publicAccount.id,
    result: "success",
    reason: "初始化平台超管与首个机构",
  });

  return { account: publicAccount, session };
}

export async function createRegistration(input: {
  name: string;
  email: string;
  password: string;
  organizationId?: string;
  invitationToken?: string;
}): Promise<{ account: PublicAccount; session?: Session }> {
  const database = getDatabase();
  const password = hashPassword(input.password);
  const expiresAt = new Date(Date.now() + SESSION_MAX_AGE_SECONDS * 1000);
  const settings = await getSystemSettings();

  const created = await database.transaction(async (transaction) => {
    const normalizedEmail = normalizeEmail(input.email);
    const existing = await transaction.select({ id: accounts.id }).from(accounts).where(eq(accounts.email, normalizedEmail));
    if (existing.length > 0) {
      return null;
    }

    const invitationRows = input.invitationToken
      ? await transaction
          .select({ invitation: organizationInvitations, organization: organizations, role: roles })
          .from(organizationInvitations)
          .innerJoin(organizations, eq(organizations.id, organizationInvitations.organizationId))
          .innerJoin(roles, eq(roles.id, organizationInvitations.roleId))
          .where(eq(organizationInvitations.token, input.invitationToken))
          .limit(1)
      : [];
    const invitation = invitationRows[0];
    const now = new Date();
    if (input.invitationToken && !invitation) {
      throw new Error("邀请链接无效");
    }
    if (
      invitation &&
      (invitation.invitation.status !== "active" ||
        invitation.invitation.expiresAt < now ||
        invitation.invitation.usedCount >= invitation.invitation.maxUses)
    ) {
      throw new Error("邀请链接已失效");
    }
    if (invitation?.invitation.email && normalizeEmail(invitation.invitation.email) !== normalizedEmail) {
      throw new Error("邀请邮箱与注册邮箱不一致");
    }
    if (!invitation && !settings.registrationEnabled) {
      throw new Error("系统暂未开放注册");
    }

    const targetOrganizationId = invitation?.invitation.organizationId ?? input.organizationId;
    if (targetOrganizationId && !invitation) {
      const organizationRows = await transaction
        .select()
        .from(organizations)
        .where(eq(organizations.id, targetOrganizationId))
        .limit(1);
      const organization = organizationRows[0];
      if (!organization) {
        throw new Error("机构不存在");
      }
      if (!organization.registrationEnabled) {
        throw new Error("该机构暂未开放注册");
      }
    }

    const accountRows = await transaction
      .insert(accounts)
      .values({
        name: input.name.trim(),
        email: normalizedEmail,
        passwordHash: password.hash,
        passwordSalt: password.salt,
        status: "active",
      })
      .returning();
    const account = accountRows[0];
    if (!account) {
      throw new Error("账号创建失败");
    }

    if (invitation) {
      await transaction.insert(memberships).values({
        accountId: account.id,
        organizationId: invitation.invitation.organizationId,
        roleId: invitation.invitation.roleId,
        status: "active",
      });
      const consumedRows = await transaction
        .update(organizationInvitations)
        .set({
          usedCount: sql`${organizationInvitations.usedCount} + 1`,
          updatedAt: new Date(),
        })
        .where(
          and(
            eq(organizationInvitations.id, invitation.invitation.id),
            eq(organizationInvitations.status, "active"),
            gte(organizationInvitations.expiresAt, now),
            lt(organizationInvitations.usedCount, organizationInvitations.maxUses),
          ),
        )
        .returning({
          id: organizationInvitations.id,
          maxUses: organizationInvitations.maxUses,
          usedCount: organizationInvitations.usedCount,
        });
      const consumed = consumedRows[0];
      if (!consumed) {
        throw new Error("邀请链接已失效");
      }
      if (consumed.usedCount >= consumed.maxUses) {
        await transaction
          .update(organizationInvitations)
          .set({
            status: "accepted",
            acceptedByAccountId: account.id,
            acceptedAt: new Date(),
            updatedAt: new Date(),
          })
          .where(eq(organizationInvitations.id, consumed.id));
      }
    }

    const sessionRows = await transaction
      .insert(sessions)
      .values({
        accountId: account.id,
        activeOrganizationId: invitation?.invitation.organizationId,
        expiresAt,
      })
      .returning();
    const session = sessionRows[0];

    if (targetOrganizationId && !invitation) {
      await transaction.insert(joinRequests).values({
        accountId: account.id,
        organizationId: targetOrganizationId,
        status: "pending",
        reason: "公开注册申请加入机构",
      });
    }

    return { account, organization: invitation?.organization, role: invitation?.role, session };
  });

  if (!created) {
    throw new Error("账号已存在");
  }

  const organization = created.organization ? toOrganization(created.organization) : undefined;
  const publicAccount = toPublicAccount(created.account, created.role?.key, organization);
  const session = created.session ? toSession(created.session) : undefined;
  await recordAuditLog({
    actor: publicAccount,
    organizationId: organization?.id ?? input.organizationId,
    action: "account.register",
    targetType: "account",
    targetId: publicAccount.id,
    result: "success",
    reason: input.invitationToken ? "通过邀请注册并加入机构" : input.organizationId ? "注册账号并申请加入机构" : "注册独立账号",
  });

  return { account: publicAccount, session };
}

export async function loginWithPassword(input: {
  email: string;
  password: string;
}): Promise<{ account: PublicAccount; session: Session }> {
  const database = getDatabase();
  const accountRows = await database
    .select()
    .from(accounts)
    .where(eq(accounts.email, normalizeEmail(input.email)))
    .limit(1);
  const account = accountRows[0];

  if (!account || account.status !== "active" || !verifyPassword(input.password, account)) {
    throw new Error("邮箱或密码错误");
  }

  const membershipRows = await database
    .select({ membership: memberships, organization: organizations, role: roles })
    .from(memberships)
    .innerJoin(organizations, eq(organizations.id, memberships.organizationId))
    .innerJoin(roles, eq(roles.id, memberships.roleId))
    .where(and(eq(memberships.accountId, account.id), eq(memberships.status, "active"), eq(roles.isEnabled, true)))
    .limit(1);
  const membership = membershipRows[0];
  const expiresAt = new Date(Date.now() + SESSION_MAX_AGE_SECONDS * 1000);
  const sessionRows = await database
    .insert(sessions)
    .values({
      accountId: account.id,
      activeOrganizationId: membership?.organization.id,
      expiresAt,
    })
    .returning();
  const sessionRow = sessionRows[0];
  if (!sessionRow) {
    throw new Error("会话创建失败");
  }

  const platformRoleRows = account.platformRoleId
    ? await database.select().from(roles).where(eq(roles.id, account.platformRoleId)).limit(1)
    : [];
  const platformRole = platformRoleRows[0];
  const organization = membership ? toOrganization(membership.organization) : undefined;
  const publicAccount = toPublicAccount(account, platformRole?.key ?? membership?.role.key, organization);
  const session = toSession(sessionRow);

  await recordAuditLog({
    actor: publicAccount,
    organizationId: organization?.id,
    action: "account.login",
    targetType: "account",
    targetId: account.id,
    result: "success",
    reason: "账号登录",
  });

  return { account: publicAccount, session };
}

export async function getCurrentAuthContext(): Promise<AuthContext | null> {
  const cookieStore = await cookies();
  const sessionId = cookieStore.get(SESSION_COOKIE)?.value;

  if (!sessionId) {
    return null;
  }

  const database = getDatabase();
  const rows = await database
    .select({ session: sessions, account: accounts })
    .from(sessions)
    .innerJoin(accounts, eq(accounts.id, sessions.accountId))
    .where(and(eq(sessions.id, sessionId), gt(sessions.expiresAt, new Date())))
    .limit(1);
  const row = rows[0];
  if (!row || row.account.status === "disabled") {
    return null;
  }

  const session = toSession(row.session);
  const platformRoleRows = row.account.platformRoleId
    ? await database.select().from(roles).where(and(eq(roles.id, row.account.platformRoleId), eq(roles.isEnabled, true))).limit(1)
    : [];
  const platformRole = platformRoleRows[0];
  const organizationRows = row.session.activeOrganizationId
    ? await database.select().from(organizations).where(eq(organizations.id, row.session.activeOrganizationId)).limit(1)
    : [];
  const organizationRow = organizationRows[0];
  const membershipRows = organizationRow
    ? await database
        .select({ membership: memberships, role: roles })
        .from(memberships)
        .innerJoin(roles, eq(roles.id, memberships.roleId))
        .where(
          and(
            eq(memberships.accountId, row.account.id),
            eq(memberships.organizationId, organizationRow.id),
            eq(memberships.status, "active"),
            eq(roles.isEnabled, true),
          ),
        )
        .limit(1)
    : [];
  const membershipRow = membershipRows[0];

  const platformPermissions = platformRole ? await getPermissionsForRole(platformRole.id) : [];
  const organizationPermissions = membershipRow ? await getPermissionsForRole(membershipRow.role.id) : [];
  const permissions = [...new Set([...platformPermissions, ...organizationPermissions])];
  const organization = organizationRow ? toOrganization(organizationRow) : undefined;
  const membership = membershipRow ? toMembership(membershipRow.membership, membershipRow.role) : undefined;
  const canAccessAllOrganizations =
    platformPermissions.includes("platform:manage") || platformPermissions.includes("organization:read");
  const organizationOptions = canAccessAllOrganizations
    ? (
        await database
          .select()
          .from(organizations)
          .where(eq(organizations.status, "active"))
      ).map((item) => toOrganizationOption(item, platformRole?.label ?? "平台账号", row.session.activeOrganizationId))
    : (
        await database
          .select({ membership: memberships, organization: organizations, role: roles })
          .from(memberships)
          .innerJoin(organizations, eq(organizations.id, memberships.organizationId))
          .innerJoin(roles, eq(roles.id, memberships.roleId))
          .where(
            and(
              eq(memberships.accountId, row.account.id),
              eq(memberships.status, "active"),
              eq(organizations.status, "active"),
              eq(roles.isEnabled, true),
            ),
          )
      ).map((item) => toOrganizationOption(item.organization, item.role.label, row.session.activeOrganizationId));

  return {
    account: toPublicAccount(row.account, platformRole?.key ?? membership?.roleKey, organization),
    organization,
    organizations: organizationOptions,
    membership,
    permissions,
    session,
  };
}

export async function switchActiveOrganization(organizationId: string): Promise<AuthContext> {
  const context = await getCurrentAuthContext();
  if (!context) {
    throw new Error("未登录或会话已过期");
  }

  const target = context.organizations.find((organization) => organization.id === organizationId);
  if (!target) {
    throw new Error("无权切换到该机构");
  }

  const database = getDatabase();
  await database.update(sessions).set({ activeOrganizationId: organizationId }).where(eq(sessions.id, context.session.id));

  await recordAuditLog({
    actor: context.account,
    organizationId,
    action: "session.organization.switch",
    targetType: "organization",
    targetId: organizationId,
    result: "success",
    reason: `切换机构：${target.name}`,
  });

  const nextContext = await getCurrentAuthContext();
  if (!nextContext) {
    throw new Error("机构已切换，请重新登录");
  }

  return nextContext;
}

export async function removeCurrentSession(): Promise<PublicAccount | null> {
  const context = await getCurrentAuthContext();
  const cookieStore = await cookies();
  const sessionId = cookieStore.get(SESSION_COOKIE)?.value;

  if (sessionId) {
    const database = getDatabase();
    await database.delete(sessions).where(eq(sessions.id, sessionId));
  }

  await clearSessionCookie();
  return context?.account ?? null;
}

type PermissionCheckSuccess = {
  success: true;
  context: AuthContext;
};

type PermissionCheckFailure = {
  success: false;
  response: Response;
};

type DeniedAuditContext = {
  action: string;
  targetType: string;
  targetId?: string;
  organizationId?: string;
};

export async function requirePermission(
  permission: Permission,
  auditContext: DeniedAuditContext,
): Promise<PermissionCheckSuccess | PermissionCheckFailure> {
  const context = await getCurrentAuthContext();

  if (!context) {
    await recordAuditLog({
      organizationId: auditContext.organizationId,
      action: auditContext.action,
      targetType: auditContext.targetType,
      targetId: auditContext.targetId,
      result: "denied",
      reason: "未登录或会话已过期",
    });

    return {
      success: false,
      response: jsonFailure("未登录或会话已过期", 401),
    };
  }

  if (!hasPermission(context.permissions, permission)) {
    await recordAuditLog({
      actor: context.account,
      organizationId: auditContext.organizationId ?? context.organization?.id,
      action: auditContext.action,
      targetType: auditContext.targetType,
      targetId: auditContext.targetId,
      result: "denied",
      reason: `缺少权限：${permission}`,
    });

    return {
      success: false,
      response: jsonFailure("权限不足", 403),
    };
  }

  return { success: true, context };
}
