import logging
import os

import allure
import pytest
from frostfs_testlib import reporter
from frostfs_testlib.resources.wellknown_acl import PUBLIC_ACL
from frostfs_testlib.steps.cli.container import (
    create_container,
    delete_container,
    list_containers,
    wait_for_container_deletion,
)
from frostfs_testlib.steps.cli.object import delete_object
from frostfs_testlib.steps.http.http_gate import (
    attr_into_str_header_curl,
    get_object_by_attr_and_verify_hashes,
    try_to_get_object_and_expect_error,
    try_to_get_object_via_passed_request_and_expect_error,
    upload_via_http_gate_curl,
)
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.storage_object_info import StorageObjectInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.utils.file_utils import generate_file

OBJECT_ALREADY_REMOVED_ERROR = "object already removed"
logger = logging.getLogger("NeoLogger")


@pytest.mark.skip("Skipped temporarly")
@pytest.mark.http_gate
@pytest.mark.http_put
class Test_http_headers(ClusterTestBase):
    PLACEMENT_RULE = "REP 2 IN X CBF 1 SELECT 4 FROM * AS X"
    obj1_keys = ["Writer", "Chapter1", "Chapter2"]
    obj2_keys = ["Writer", "Ch@pter1", "chapter2"]
    values = ["Leo Tolstoy", "peace", "w@r"]
    OBJECT_ATTRIBUTES = [
        {obj1_keys[0]: values[0], obj1_keys[1]: values[1], obj1_keys[2]: values[2]},
        {obj2_keys[0]: values[0], obj2_keys[1]: values[1], obj2_keys[2]: values[2]},
    ]

    @pytest.fixture(scope="class", autouse=True)
    @allure.title("[Class/Autouse]: Prepare wallet and deposit")
    def prepare_wallet(self, default_wallet):
        Test_http_headers.wallet = default_wallet

    def storage_objects_with_attributes(self, object_size: ObjectSize) -> list[StorageObjectInfo]:
        # TODO: Deal with http tests
        if object_size.value > 1000:
            pytest.skip("Complex objects for HTTP temporarly disabled for v0.37")

        storage_objects = []
        wallet = self.wallet
        cid = create_container(
            wallet=self.wallet,
            shell=self.shell,
            endpoint=self.cluster.default_rpc_endpoint,
            rule=self.PLACEMENT_RULE,
            basic_acl=PUBLIC_ACL,
        )
        file_path = generate_file(object_size.value)
        for attributes in self.OBJECT_ATTRIBUTES:
            storage_object_id = upload_via_http_gate_curl(
                cid=cid,
                filepath=file_path,
                endpoint=self.cluster.default_http_gate_endpoint,
                headers=attr_into_str_header_curl(attributes),
            )
            storage_object = StorageObjectInfo(cid, storage_object_id)
            storage_object.size = os.path.getsize(file_path)
            storage_object.wallet_file_path = wallet
            storage_object.file_path = file_path
            storage_object.attributes = attributes

            storage_objects.append(storage_object)

        yield storage_objects

    @allure.title("Get object1 by attribute")
    def test_object1_can_be_get_by_attr(self, storage_objects_with_attributes: list[StorageObjectInfo]):
        """
        Test to get object#1 by attribute and comapre hashes

        Steps:
        1. Download object#1 with attributes [Chapter2=w@r] and compare hashes
        """

        storage_object_1 = storage_objects_with_attributes[0]

        with reporter.step(
            f'Download object#1 via wget with attributes Chapter2: {storage_object_1.attributes["Chapter2"]} and compare hashes'
        ):
            get_object_by_attr_and_verify_hashes(
                oid=storage_object_1.oid,
                file_name=storage_object_1.file_path,
                cid=storage_object_1.cid,
                attrs={"Chapter2": storage_object_1.attributes["Chapter2"]},
                node=self.cluster.cluster_nodes[0],
            )

    @allure.title("Get object2 with different attributes, then delete object2 and get object1")
    def test_object2_can_be_get_by_attr(self, storage_objects_with_attributes: list[StorageObjectInfo]):
        """
        Test to get object2 with different attributes, then delete object2 and get object1 using 1st attribute. Note: obj1 and obj2 have the same attribute#1,
        and when obj2 is deleted you can get obj1 by 1st attribute

        Steps:
        1. Download object#2 with attributes [chapter2=w@r] and compare hashes
        2. Download object#2 with attributes [Ch@pter1=peace] and compare hashes
        3. Delete object#2
        4. Download object#1 with attributes [Writer=Leo Tolstoy] and compare hashes
        """
        storage_object_1 = storage_objects_with_attributes[0]
        storage_object_2 = storage_objects_with_attributes[1]

        with reporter.step(
            f'Download object#2 via wget with attributes [chapter2={storage_object_2.attributes["chapter2"]}] / [Ch@pter1={storage_object_2.attributes["Ch@pter1"]}]  and compare hashes'
        ):
            selected_attributes_object2 = [
                {"chapter2": storage_object_2.attributes["chapter2"]},
                {"Ch@pter1": storage_object_2.attributes["Ch@pter1"]},
            ]
            for attributes in selected_attributes_object2:
                get_object_by_attr_and_verify_hashes(
                    oid=storage_object_2.oid,
                    file_name=storage_object_2.file_path,
                    cid=storage_object_2.cid,
                    attrs=attributes,
                    node=self.cluster.cluster_nodes[0],
                )
        with reporter.step("Delete object#2 and verify is the container deleted"):
            delete_object(
                wallet=self.wallet,
                cid=storage_object_2.cid,
                oid=storage_object_2.oid,
                shell=self.shell,
                endpoint=self.cluster.default_rpc_endpoint,
            )
            try_to_get_object_and_expect_error(
                cid=storage_object_2.cid,
                oid=storage_object_2.oid,
                node=self.cluster.cluster_nodes[0],
                error_pattern=OBJECT_ALREADY_REMOVED_ERROR,
            )
            storage_objects_with_attributes.remove(storage_object_2)

        with reporter.step(
            f'Download object#1 with attributes [Writer={storage_object_1.attributes["Writer"]}] and compare hashes'
        ):
            key_value_pair = {"Writer": storage_object_1.attributes["Writer"]}
            get_object_by_attr_and_verify_hashes(
                oid=storage_object_1.oid,
                file_name=storage_object_1.file_path,
                cid=storage_object_1.cid,
                attrs=key_value_pair,
                node=self.cluster.cluster_nodes[0],
            )

    @allure.title("[NEGATIVE] Put object and get right after container is deleted")
    def test_negative_put_and_get_object3(self, storage_objects_with_attributes: list[StorageObjectInfo]):
        """
        Test to attempt to put object and try to download it right after the container has been deleted

        Steps:
        1. [Negative] Allocate and attempt to put object#3 via http with attributes: [Writer=Leo Tolstoy, Writer=peace, peace=peace]
            Expected: "Error duplication of attributes detected"
        2. Delete container
        3. [Negative] Try to download object with attributes [peace=peace]
            Expected: "HTTP request sent, awaiting response... 404 Not Found"
        """
        storage_object_1 = storage_objects_with_attributes[0]

        with reporter.step(
            "[Negative] Allocate and attemt to put object#3 via http with attributes: [Writer=Leo Tolstoy, Writer=peace, peace=peace]"
        ):
            file_path_3 = generate_file(storage_object_1.size)
            attrs_obj3 = {"Writer": "Leo Tolstoy", "peace": "peace"}
            headers = attr_into_str_header_curl(attrs_obj3)
            headers.append(" ".join(attr_into_str_header_curl({"Writer": "peace"})))
            error_pattern = f"key duplication error: X-Attribute-Writer"
            upload_via_http_gate_curl(
                cid=storage_object_1.cid,
                filepath=file_path_3,
                endpoint=self.cluster.default_http_gate_endpoint,
                headers=headers,
                error_pattern=error_pattern,
            )
        with reporter.step("Delete container and verify container deletion"):
            delete_container(
                wallet=self.wallet,
                cid=storage_object_1.cid,
                shell=self.shell,
                endpoint=self.cluster.default_rpc_endpoint,
                await_mode=True,
            )
            self.tick_epoch()
            wait_for_container_deletion(
                self.wallet,
                storage_object_1.cid,
                shell=self.shell,
                endpoint=self.cluster.default_rpc_endpoint,
            )
            assert storage_object_1.cid not in list_containers(
                self.wallet, shell=self.shell, endpoint=self.cluster.default_rpc_endpoint
            )
        with reporter.step("[Negative] Try to download (wget) object via wget with attributes [peace=peace]"):
            request = f"/get/{storage_object_1.cid}/peace/peace"
            error_pattern = "404 Not Found"
            try_to_get_object_via_passed_request_and_expect_error(
                cid=storage_object_1.cid,
                oid="",
                node=self.cluster.cluster_nodes[0],
                error_pattern=error_pattern,
                attrs=attrs_obj3,
                http_request_path=request,
            )