import torch


def SMO(
    X: torch.Tensor,
    y: torch.Tensor,
    kernel,
    C: float,
    tol: float = 1e-3,
    max_passes: int = 5,
    is_l2: bool = False,
):
    n = X.shape[0]
    device, dtype = X.device, X.dtype

    alphas = torch.zeros(n, device=device, dtype=dtype)
    b = 0.0

    if is_l2:
        C_bound = float("inf")
        l2_shift = 1.0 / (2 * C)
    else:
        C_bound = C
        l2_shift = 0.0

    E = -y.clone().to(dtype)
    eps = 1e-8

    def take_step(i, j):
        nonlocal b, E

        if i == j:
            return 0

        alpha_i, alpha_j = alphas[i].item(), alphas[j].item()
        y_i, y_j = y[i].item(), y[j].item()
        E_i, E_j = E[i].item(), E[j].item()

        if y_i != y_j:
            L = max(0.0, alpha_j - alpha_i)
            H = min(C_bound, C_bound + alpha_j - alpha_i)
        else:
            L = max(0.0, alpha_i + alpha_j - C_bound)
            H = min(C_bound, alpha_i + alpha_j)

        if abs(L - H) < eps:
            return 0

        row_i = kernel[i]
        row_j = kernel[j]

        K_ii, K_jj, K_ij = row_i[i].item(), row_j[j].item(), row_i[j].item()

        eta = 2.0 * K_ij - (K_ii + l2_shift) - (K_jj + l2_shift)
        if eta >= 0:
            return 0

        new_alpha_j = max(L, min(H, alpha_j - (y_j * (E_i - E_j)) / eta))

        if abs(new_alpha_j - alpha_j) < 1e-5:
            return 0

        new_alpha_i = alpha_i + y_i * y_j * (alpha_j - new_alpha_j)

        delta_alpha_i = new_alpha_i - alpha_i
        delta_alpha_j = new_alpha_j - alpha_j

        b1 = (
            b
            - E_i
            - y_i * delta_alpha_i * (K_ii + l2_shift)
            - y_j * delta_alpha_j * K_ij
        )
        b2 = (
            b
            - E_j
            - y_i * delta_alpha_i * K_ij
            - y_j * delta_alpha_j * (K_jj + l2_shift)
        )

        b_old = b
        if eps < new_alpha_i < C_bound - eps:
            b = b1
        elif eps < new_alpha_j < C_bound - eps:
            b = b2
        else:
            b = (b1 + b2) / 2.0

        E += (
            y_i * delta_alpha_i * row_i
            + y_j * delta_alpha_j * row_j
            + (b - b_old)
        )

        if is_l2:
            E[i] += y_i * delta_alpha_i * l2_shift
            E[j] += y_j * delta_alpha_j * l2_shift

        alphas[i], alphas[j] = new_alpha_i, new_alpha_j
        return 1

    def examine_example(i):
        y_i = y[i].item()
        alpha_i = alphas[i].item()
        E_i = E[i].item()
        r_i = E_i * y_i

        if (r_i < -tol and alpha_i < C_bound - eps) or (
            r_i > tol and alpha_i > eps
        ):
            non_bound_mask = (alphas > eps) & (alphas < C_bound - eps)
            non_bound_idx = torch.where(non_bound_mask)[0]

            if non_bound_idx.numel() > 1:
                E_non_bound = E[non_bound_idx]
                j = non_bound_idx[
                    (
                        torch.argmin(E_non_bound)
                        if E_i >= 0
                        else torch.argmax(E_non_bound)
                    )
                ].item()
                if take_step(i, j):
                    return 1

                perm = torch.randperm(non_bound_idx.size(0))
                for j_idx in non_bound_idx[perm].tolist():
                    if take_step(i, j_idx):
                        return 1

            all_idx = torch.randperm(n).tolist()
            for j_idx in all_idx:
                if take_step(i, j_idx):
                    return 1
        return 0

    num_changed, examine_all = 0, True
    max_iterations = max(50, max_passes * 10)
    iterations = 0

    while (num_changed > 0 or examine_all) and iterations < max_iterations:
        num_changed = 0
        if examine_all:
            for i in range(n):
                num_changed += examine_example(i)
        else:
            non_bound_idx = torch.where(
                (alphas > eps) & (alphas < C_bound - eps)
            )[0]
            for i in non_bound_idx.tolist():
                num_changed += examine_example(i)

        if examine_all:
            examine_all = False
        elif num_changed == 0:
            examine_all = True
        iterations += 1

    return alphas * y, b
