# 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.

"""DTO and resource-path unit tests for Catalog branch CRUD wire format.

These tests pin the Python wire format to the Java contract so review
against ``paimon-api/.../rest/requests/CreateBranchRequest.java``,
``RenameBranchRequest.java``, ``ForwardBranchRequest.java``,
``responses/ListBranchesResponse.java`` and ``ResourcePaths.java`` does
not need to inspect a running mock server. They explicitly assert the
*exact* JSON field names and URL templates Java uses, and that
``ListBranchesResponse`` is **not** paged (Java's ``listBranches``
returns plain ``List<String>`` — pypaimon mirrors this).
"""

import json
import unittest

from pypaimon.api.api_request import (CreateBranchRequest, ForwardBranchRequest,
                                      RenameBranchRequest)
from pypaimon.api.api_response import ListBranchesResponse
from pypaimon.api.resource_paths import ResourcePaths
from pypaimon.common.json_util import JSON


class CreateBranchRequestSerdeTest(unittest.TestCase):

    def test_with_tag_uses_java_field_names(self):
        request = CreateBranchRequest(branch="b1", from_tag="t1")
        parsed = json.loads(JSON.to_json(request))
        # Field names are Java's ``branch`` / ``fromTag`` — not ``branch_name``.
        self.assertEqual(parsed["branch"], "b1")
        self.assertEqual(parsed["fromTag"], "t1")
        self.assertEqual(set(parsed.keys()), {"branch", "fromTag"})

    def test_without_tag_serializes_null_from_tag(self):
        request = CreateBranchRequest(branch="b1")
        parsed = json.loads(JSON.to_json(request))
        self.assertEqual(parsed["branch"], "b1")
        # ``fromTag`` is included as null when not set (matches Java
        # @Nullable behavior; jackson serializes nullable Long as null).
        self.assertIsNone(parsed.get("fromTag"))


class RenameBranchRequestSerdeTest(unittest.TestCase):

    def test_uses_to_branch_field(self):
        request = RenameBranchRequest(to_branch="b2")
        parsed = json.loads(JSON.to_json(request))
        self.assertEqual(parsed, {"toBranch": "b2"})


class ForwardBranchRequestSerdeTest(unittest.TestCase):

    def test_serializes_empty_object(self):
        # Java's ForwardBranchRequest is an empty body. The Python wire DTO
        # must serialize to {} (not null, not an array, not absent).
        rendered = JSON.to_json(ForwardBranchRequest())
        self.assertEqual(json.loads(rendered), {})


class ListBranchesResponseSerdeTest(unittest.TestCase):

    def test_from_json_populates_branches(self):
        response = JSON.from_json(
            json.dumps({"branches": ["b1", "b2"]}),
            ListBranchesResponse,
        )
        self.assertEqual(response.branches, ["b1", "b2"])

    def test_response_is_not_paged(self):
        # ListBranchesResponse must NOT be a paged response — Java's
        # ListBranchesResponse has no ``nextPageToken``. This locks down
        # the contract; if a future change accidentally extends it to
        # PagedResponse this test fails.
        response = ListBranchesResponse(branches=["b1"])
        self.assertFalse(hasattr(response, "next_page_token"))
        # Serialized form must not contain the paging key.
        self.assertNotIn("nextPageToken", JSON.to_json(response))


class ResourcePathsBranchesTest(unittest.TestCase):

    def setUp(self):
        self.paths = ResourcePaths(prefix="mock")

    def test_branches_collection_url(self):
        self.assertEqual(
            self.paths.branches("db", "tbl"),
            "/v1/mock/databases/db/tables/tbl/branches",
        )

    def test_single_branch_url(self):
        self.assertEqual(
            self.paths.branch("db", "tbl", "b1"),
            "/v1/mock/databases/db/tables/tbl/branches/b1",
        )

    def test_rename_branch_url(self):
        self.assertEqual(
            self.paths.rename_branch("db", "tbl", "b1"),
            "/v1/mock/databases/db/tables/tbl/branches/b1/rename",
        )

    def test_forward_branch_url(self):
        self.assertEqual(
            self.paths.forward_branch("db", "tbl", "b1"),
            "/v1/mock/databases/db/tables/tbl/branches/b1/forward",
        )

    def test_branch_url_url_encodes_branch_name(self):
        # RESTUtil.encode_string escapes characters that are not URL-safe;
        # a space round-trips to ``%20``. Mirrors the existing tag-name path.
        self.assertEqual(
            self.paths.branch("db", "tbl", "release 1.0"),
            "/v1/mock/databases/db/tables/tbl/branches/release%201.0",
        )


if __name__ == "__main__":
    unittest.main()
