"use server"

import { revalidatePath } from "next/cache"
import { after } from "next/server"

import type { ActionState } from "@/shared/types/action-state"
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
import { Permissions, type Permission } from "@/shared/types/permissions"
import { logAudit } from "@/shared/lib/audit-logger"
import { logDataChange } from "@/shared/lib/change-logger"

import {
  AssignUserRolesSchema,
  CreateRoleSchema,
  SetRolePermissionsSchema,
  UpdateRoleSchema,
} from "./schema"
import {
  ADMIN_ROLE_NAME,
  createRole,
  deleteRole,
  getRoleById,
  getRolePermissions,
  setRoleEnabled,
  setRolePermissions,
  updateRole,
} from "./data-access"
import { assignRolesToUser, getUserRoleNames } from "./data-access-assignments"
import { trackPermissionChange } from "./lib/track"
import type { RoleDetail, RoleRecord } from "./types"

/**
 * Create a new custom role.
 */
export async function createRoleAction(
  prevState: ActionState<RoleRecord> | undefined,
  formData: FormData
): Promise<ActionState<RoleRecord>> {
  try {
    await requirePermission(Permissions.ROLE_CREATE)

    const parsed = CreateRoleSchema.safeParse({
      name: formData.get("name"),
      description: formData.get("description") || null,
    })
    if (!parsed.success) {
      return {
        success: false,
        message: "Invalid form data",
        errors: parsed.error.flatten().fieldErrors,
      }
    }

    const role = await createRole(parsed.data)

    after(() =>
      logAudit({
        action: "role.create",
        module: "rbac",
        targetId: role.id,
        targetType: "role",
        detail: { name: role.name, description: role.description },
      })
    )
    after(() =>
      logDataChange({
        tableName: "roles",
        recordId: role.id,
        action: "create",
        newValue: { name: role.name, description: role.description },
      })
    )

    revalidatePath("/admin/roles")
    return { success: true, data: role, message: "Role created" }
  } catch (error) {
    if (error instanceof PermissionDeniedError) {
      return { success: false, message: error.message }
    }
    if (error instanceof Error) {
      return { success: false, message: error.message }
    }
    return { success: false, message: "Failed to create role" }
  }
}

/**
 * Update a role's name and/or description.
 */
export async function updateRoleAction(
  roleId: string,
  prevState: ActionState<RoleRecord> | undefined,
  formData: FormData
): Promise<ActionState<RoleRecord>> {
  try {
    await requirePermission(Permissions.ROLE_UPDATE)

    const parsed = UpdateRoleSchema.safeParse({
      name: formData.get("name") || undefined,
      description: formData.get("description") || null,
    })
    if (!parsed.success) {
      return {
        success: false,
        message: "Invalid form data",
        errors: parsed.error.flatten().fieldErrors,
      }
    }

    const before = await getRoleById(roleId)
    if (!before) return { success: false, message: "Role not found" }

    const role = await updateRole(roleId, parsed.data)

    after(() =>
      logAudit({
        action: "role.update",
        module: "rbac",
        targetId: role.id,
        targetType: "role",
        detail: { before: { name: before.name, description: before.description }, after: { name: role.name, description: role.description } },
      })
    )
    after(() =>
      logDataChange({
        tableName: "roles",
        recordId: role.id,
        action: "update",
        oldValue: { name: before.name, description: before.description },
        newValue: { name: role.name, description: role.description },
      })
    )

    revalidatePath("/admin/roles")
    revalidatePath(`/admin/roles/${roleId}`)
    return { success: true, data: role, message: "Role updated" }
  } catch (error) {
    if (error instanceof PermissionDeniedError) {
      return { success: false, message: error.message }
    }
    if (error instanceof Error) {
      return { success: false, message: error.message }
    }
    return { success: false, message: "Failed to update role" }
  }
}

/**
 * Delete a custom role. System roles cannot be deleted.
 */
export async function deleteRoleAction(
  roleId: string
): Promise<ActionState<void>> {
  try {
    await requirePermission(Permissions.ROLE_DELETE)

    const before = await getRoleById(roleId)
    if (!before) return { success: false, message: "Role not found" }

    await deleteRole(roleId)

    after(() =>
      logAudit({
        action: "role.delete",
        module: "rbac",
        targetId: roleId,
        targetType: "role",
        detail: { name: before.name },
      })
    )
    after(() =>
      logDataChange({
        tableName: "roles",
        recordId: roleId,
        action: "delete",
        oldValue: { name: before.name, description: before.description },
      })
    )

    revalidatePath("/admin/roles")
    return { success: true, message: "Role deleted" }
  } catch (error) {
    if (error instanceof PermissionDeniedError) {
      return { success: false, message: error.message }
    }
    if (error instanceof Error) {
      return { success: false, message: error.message }
    }
    return { success: false, message: "Failed to delete role" }
  }
}

/**
 * Enable or disable a role. The admin role cannot be disabled.
 */
export async function toggleRoleEnabledAction(
  roleId: string,
  enabled: boolean
): Promise<ActionState<RoleRecord>> {
  try {
    await requirePermission(Permissions.ROLE_UPDATE)

    const role = await setRoleEnabled(roleId, enabled)

    after(() =>
      logAudit({
        action: enabled ? "role.enable" : "role.disable",
        module: "rbac",
        targetId: roleId,
        targetType: "role",
        detail: { name: role.name },
      })
    )

    revalidatePath("/admin/roles")
    revalidatePath(`/admin/roles/${roleId}`)
    return { success: true, data: role, message: enabled ? "Role enabled" : "Role disabled" }
  } catch (error) {
    if (error instanceof PermissionDeniedError) {
      return { success: false, message: error.message }
    }
    if (error instanceof Error) {
      return { success: false, message: error.message }
    }
    return { success: false, message: "Failed to toggle role" }
  }
}

/**
 * Replace the full permission list for a role.
 * The admin role's permissions cannot be changed.
 */
export async function setRolePermissionsAction(
  prevState: ActionState<Permission[]> | undefined,
  formData: FormData
): Promise<ActionState<Permission[]>> {
  try {
    await requirePermission(Permissions.ROLE_UPDATE)

    const parsed = SetRolePermissionsSchema.safeParse({
      roleId: formData.get("roleId"),
      permissions: JSON.parse(String(formData.get("permissions") ?? "[]")),
    })
    if (!parsed.success) {
      return {
        success: false,
        message: "Invalid input",
        errors: parsed.error.flatten().fieldErrors,
      }
    }

    const before = await getRolePermissions(parsed.data.roleId)
    // Schema validates all strings are valid permission values, so the cast is safe.
    const permissions = parsed.data.permissions as Permission[]
    await setRolePermissions(parsed.data.roleId, permissions)

    trackPermissionChange({
      action: "set_role_permissions",
      roleId: parsed.data.roleId,
      before,
      after: permissions,
    })

    after(() =>
      logAudit({
        action: "role.set_permissions",
        module: "rbac",
        targetId: parsed.data.roleId,
        targetType: "role",
        detail: {
          before,
          after: permissions,
        },
      })
    )
    after(() =>
      logDataChange({
        tableName: "role_permissions",
        recordId: parsed.data.roleId,
        action: "update",
        oldValue: { permissions: before },
        newValue: { permissions },
      })
    )

    revalidatePath("/admin/roles")
    revalidatePath(`/admin/roles/${parsed.data.roleId}`)
    return { success: true, data: permissions, message: "Permissions updated" }
  } catch (error) {
    if (error instanceof PermissionDeniedError) {
      return { success: false, message: error.message }
    }
    if (error instanceof Error) {
      return { success: false, message: error.message }
    }
    return { success: false, message: "Failed to update permissions" }
  }
}

/**
 * Assign roles to a user (full replacement).
 */
export async function assignUserRolesAction(
  prevState: ActionState<string[]> | undefined,
  formData: FormData
): Promise<ActionState<string[]>> {
  try {
    await requirePermission(Permissions.ROLE_ASSIGN)

    const parsed = AssignUserRolesSchema.safeParse({
      userId: formData.get("userId"),
      roleNames: JSON.parse(String(formData.get("roleNames") ?? "[]")),
    })
    if (!parsed.success) {
      return {
        success: false,
        message: "Invalid input",
        errors: parsed.error.flatten().fieldErrors,
      }
    }

    const before = await getUserRoleNames(parsed.data.userId)
    await assignRolesToUser(parsed.data.userId, parsed.data.roleNames)

    trackPermissionChange({
      action: "assign_user_roles",
      userId: parsed.data.userId,
      before,
      after: parsed.data.roleNames,
    })

    after(() =>
      logAudit({
        action: "user.assign_roles",
        module: "rbac",
        targetId: parsed.data.userId,
        targetType: "user",
        detail: { before, after: parsed.data.roleNames },
      })
    )
    after(() =>
      logDataChange({
        tableName: "users_to_roles",
        recordId: parsed.data.userId,
        action: "update",
        oldValue: { roles: before },
        newValue: { roles: parsed.data.roleNames },
      })
    )

    revalidatePath("/admin/users")
    return {
      success: true,
      data: parsed.data.roleNames,
      message: "Roles assigned. The user may need to sign in again for changes to take effect.",
    }
  } catch (error) {
    if (error instanceof PermissionDeniedError) {
      return { success: false, message: error.message }
    }
    if (error instanceof Error) {
      return { success: false, message: error.message }
    }
    return { success: false, message: "Failed to assign roles" }
  }
}

/**
 * Get a role detail (for the edit page). Throws PermissionDeniedError if the
 * caller lacks ROLE_READ.
 */
export async function getRoleDetailAction(
  roleId: string
): Promise<ActionState<RoleDetail>> {
  try {
    await requirePermission(Permissions.ROLE_READ)
    const role = await getRoleById(roleId)
    if (!role) return { success: false, message: "Role not found" }
    return { success: true, data: role }
  } catch (error) {
    if (error instanceof PermissionDeniedError) {
      return { success: false, message: error.message }
    }
    if (error instanceof Error) {
      return { success: false, message: error.message }
    }
    return { success: false, message: "Failed to load role" }
  }
}

/**
 * Check whether a role is the locked admin role.
 * Exported as a server action so client components can call it without
 * importing the data-access layer directly.
 */
export async function isAdminRole(roleName: string): Promise<boolean> {
  return roleName === ADMIN_ROLE_NAME
}
