# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
"""Tests for CUDA allocation pool validation."""

import pytest

from tvm.tirx.cuda.lang.alloc_pool import _validate_mma_alloc_shape
from tvm.tirx.cuda.operator.tile_primitive.tma_utils import SwizzleMode

# ---------------------------------------------------------------------------
# alloc_mma shape validation: bad inputs raise actionable ValueError instead of
# the opaque "Divide by zero" diagnostic that ``Layout.tile_to`` would emit.
# ---------------------------------------------------------------------------


class TestAllocMmaValidationRowBytes:
    """row width (cols * itemsize) must be a positive multiple of swizzle atom bytes."""

    def test_bf16_32cols_128b_swizzle_too_narrow(self):
        # The exact case that bit gdn-prefill v1_0 / v1_2 (eval R10).
        # Row = 32 * 2B = 64B < 128B atom.
        with pytest.raises(ValueError, match=r"64B rows.*128B swizzle atom"):
            _validate_mma_alloc_shape((128, 32), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM)

    def test_error_suggests_smaller_swizzle(self):
        try:
            _validate_mma_alloc_shape((128, 32), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM)
        except ValueError as e:
            assert "SWIZZLE_64B_ATOM" in str(e), f"missing fix-it hint: {e}"
        else:
            pytest.fail("should have raised")

    def test_error_suggests_widening_cols(self):
        try:
            _validate_mma_alloc_shape((128, 32), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM)
        except ValueError as e:
            assert "multiple of 64 elements" in str(e), f"missing widen hint: {e}"
        else:
            pytest.fail("should have raised")

    def test_fp32_16cols_128b_swizzle_too_narrow(self):
        # Row = 16 * 4B = 64B < 128B atom.
        with pytest.raises(ValueError, match=r"64B rows.*128B swizzle atom"):
            _validate_mma_alloc_shape((128, 16), "float32", SwizzleMode.SWIZZLE_128B_ATOM)

    def test_3d_shape_validates_last_dim(self):
        # Validation must consider shape[-1], not shape[0].
        with pytest.raises(ValueError, match=r"64B rows"):
            _validate_mma_alloc_shape((2, 128, 32), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM)


class TestAllocMmaValidationRowCount:
    """rows (shape[-2]) must be a positive multiple of the 8-row atom."""

    def test_rows_below_atom_rejected(self):
        with pytest.raises(ValueError, match=r"shape\[-2\]=4.*multiple of 8"):
            _validate_mma_alloc_shape((4, 64), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM)

    def test_rows_not_multiple_of_8_rejected(self):
        with pytest.raises(ValueError, match=r"shape\[-2\]=12.*multiple of 8"):
            _validate_mma_alloc_shape((12, 64), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM)


class TestAllocMmaValidationRank:
    """rank-1 shapes cannot be tiled with a 2-D swizzle atom."""

    def test_rank_one_rejected(self):
        with pytest.raises(ValueError, match=r"fewer than 2 dimensions"):
            _validate_mma_alloc_shape((128,), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM)


class TestAllocMmaValidationValid:
    """combinations that should succeed must not be rejected."""

    @pytest.mark.parametrize(
        "shape,dtype,mode",
        [
            # The fix path the agent should pick when row_bytes >= 128.
            ((128, 64), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM),
            ((128, 128), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM),
            # Or downgrade to a swizzle whose atom matches the row.
            ((128, 32), "bfloat16", SwizzleMode.SWIZZLE_64B_ATOM),
            ((128, 16), "bfloat16", SwizzleMode.SWIZZLE_32B_ATOM),
            # 3-D request validates the last two dims only.
            ((2, 128, 64), "bfloat16", SwizzleMode.SWIZZLE_128B_ATOM),
            # fp32 with row width >= atom.
            ((128, 32), "float32", SwizzleMode.SWIZZLE_128B_ATOM),
            # fp8 (1B) with row width >= atom.
            ((128, 128), "float8_e4m3", SwizzleMode.SWIZZLE_128B_ATOM),
        ],
    )
    def test_valid_combinations_accepted(self, shape, dtype, mode):
        _validate_mma_alloc_shape(shape, dtype, mode)

    def test_swizzle_none_skips_validation(self):
        # SWIZZLE_NONE has no atom — even otherwise-bad shapes are allowed.
        _validate_mma_alloc_shape((128, 32), "bfloat16", SwizzleMode.SWIZZLE_NONE)
        _validate_mma_alloc_shape((3, 5), "bfloat16", SwizzleMode.SWIZZLE_NONE)
        _validate_mma_alloc_shape((128,), "bfloat16", SwizzleMode.SWIZZLE_NONE)


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
