import json

import allure
import pytest
from botocore.exceptions import ClientError
from frostfs_testlib import reporter
from frostfs_testlib.s3 import S3ClientWrapper, VersioningStatus
from frostfs_testlib.s3.interfaces import BucketContainerResolver
from frostfs_testlib.steps.s3 import s3_helper
from frostfs_testlib.steps.storage_policy import get_simple_object_copies
from frostfs_testlib.storage.dataclasses.object_size import ObjectSize
from frostfs_testlib.storage.dataclasses.wallet import WalletInfo
from frostfs_testlib.testing.cluster_test_base import ClusterTestBase
from frostfs_testlib.testing.test_control import expect_not_raises
from frostfs_testlib.utils.file_utils import generate_file

from ....resources.common import S3_POLICY_FILE_LOCATION


@pytest.mark.nightly
@pytest.mark.s3_gate
@pytest.mark.parametrize("s3_policy", [S3_POLICY_FILE_LOCATION], indirect=True)
class TestS3GatePolicy(ClusterTestBase):
    @allure.title("Bucket creation with retention policy applied (s3_client={s3_client})")
    def test_s3_bucket_location(
        self,
        default_wallet: WalletInfo,
        s3_client: S3ClientWrapper,
        simple_object_size: ObjectSize,
        bucket_container_resolver: BucketContainerResolver,
    ):
        file_path_1 = generate_file(simple_object_size.value)
        file_name_1 = s3_helper.object_key_from_file_path(file_path_1)
        file_path_2 = generate_file(simple_object_size.value)
        file_name_2 = s3_helper.object_key_from_file_path(file_path_2)

        with reporter.step("Create two buckets with different bucket configuration"):
            bucket_1 = s3_client.create_bucket(location_constraint="complex")
            s3_helper.set_bucket_versioning(s3_client, bucket_1, VersioningStatus.ENABLED)
            bucket_2 = s3_client.create_bucket(location_constraint="rep-3")
            s3_helper.set_bucket_versioning(s3_client, bucket_2, VersioningStatus.ENABLED)
            list_buckets = s3_client.list_buckets()
            assert bucket_1 in list_buckets and bucket_2 in list_buckets, f"Expected two buckets {bucket_1, bucket_2}, got {list_buckets}"

        with reporter.step("Check head buckets"):
            with expect_not_raises():
                s3_client.head_bucket(bucket_1)
                s3_client.head_bucket(bucket_2)

        with reporter.step("Put objects into buckets"):
            version_id_1 = s3_client.put_object(bucket_1, file_path_1)
            version_id_2 = s3_client.put_object(bucket_2, file_path_2)
            s3_helper.check_objects_in_bucket(s3_client, bucket_1, [file_name_1])
            s3_helper.check_objects_in_bucket(s3_client, bucket_2, [file_name_2])

        with reporter.step("Check bucket location"):
            bucket_loc_1 = s3_client.get_bucket_location(bucket_1)
            bucket_loc_2 = s3_client.get_bucket_location(bucket_2)
            assert bucket_loc_1 == "complex"
            assert bucket_loc_2 == "rep-3"

        with reporter.step("Check object policy"):
            for cluster_node in self.cluster.cluster_nodes:
                cid_1 = bucket_container_resolver.resolve(cluster_node, bucket_1)
                if cid_1:
                    break
            copies_1 = get_simple_object_copies(
                wallet=default_wallet,
                cid=cid_1,
                oid=version_id_1,
                shell=self.shell,
                nodes=self.cluster.storage_nodes,
            )
            assert copies_1 == 1
            for cluster_node in self.cluster.cluster_nodes:
                cid_2 = bucket_container_resolver.resolve(cluster_node, bucket_2)
                if cid_2:
                    break
            copies_2 = get_simple_object_copies(
                wallet=default_wallet,
                cid=cid_2,
                oid=version_id_2,
                shell=self.shell,
                nodes=self.cluster.storage_nodes,
            )
            assert copies_2 == 3

    @allure.title("Bucket with unexisting location constraint (s3_client={s3_client})")
    def test_s3_bucket_wrong_location(self, s3_client: S3ClientWrapper):
        with reporter.step("Create bucket with unenxisting location constraint policy"):
            with pytest.raises(Exception):
                s3_client.create_bucket(location_constraint="UNEXISTING LOCATION CONSTRAINT")

    @allure.title("Bucket policy (s3_client={s3_client})")
    def test_s3_bucket_policy(self, s3_client: S3ClientWrapper, bucket: str):
        with reporter.step("Create bucket"):
            s3_helper.set_bucket_versioning(s3_client, bucket, VersioningStatus.ENABLED)

        with reporter.step("GetBucketPolicy"):
            with pytest.raises((RuntimeError, ClientError)):
                s3_client.get_bucket_policy(bucket)

        with reporter.step("Put new policy"):
            custom_policy = {
                "Version": "2012-10-17",
                "Id": "aaaa-bbbb-cccc-dddd",
                "Statement": [
                    {
                        "Sid": "AddPerm",
                        "Effect": "Allow",
                        "Principal": "*",
                        "Action": ["s3:GetObject"],
                        "Resource": [f"arn:aws:s3:::{bucket}/*"],
                    }
                ],
            }
            s3_client.put_bucket_policy(bucket, custom_policy)

        with reporter.step("GetBucketPolicy"):
            returned_policy = json.loads(s3_client.get_bucket_policy(bucket))
            assert returned_policy == custom_policy, "Wrong policy was received"

        with reporter.step("Delete the policy"):
            s3_client.delete_bucket_policy(bucket)

        with reporter.step("GetBucketPolicy"):
            with pytest.raises((RuntimeError, ClientError)):
                s3_client.get_bucket_policy(bucket)

    @allure.title("Bucket CORS (s3_client={s3_client})")
    def test_s3_cors(self, s3_client: S3ClientWrapper, bucket: str):
        with reporter.step("Create bucket without cors"):
            s3_helper.set_bucket_versioning(s3_client, bucket, VersioningStatus.ENABLED)

        with pytest.raises(Exception):
            bucket_cors = s3_client.get_bucket_cors(bucket)

        with reporter.step("Put bucket cors"):
            cors = {
                "CORSRules": [
                    {
                        "AllowedOrigins": ["http://www.example.com"],
                        "AllowedHeaders": ["*"],
                        "AllowedMethods": ["PUT", "POST", "DELETE"],
                        "MaxAgeSeconds": 3000,
                        "ExposeHeaders": ["x-amz-server-side-encryption"],
                    },
                    {
                        "AllowedOrigins": ["*"],
                        "AllowedHeaders": ["Authorization"],
                        "AllowedMethods": ["GET"],
                        "MaxAgeSeconds": 3000,
                    },
                ]
            }
            s3_client.put_bucket_cors(bucket, cors)
            bucket_cors = s3_client.get_bucket_cors(bucket)
            assert bucket_cors == cors.get("CORSRules"), f"Expected CORSRules must be {cors.get('CORSRules')}"

        with reporter.step("delete bucket cors"):
            s3_client.delete_bucket_cors(bucket)

        with pytest.raises(Exception):
            bucket_cors = s3_client.get_bucket_cors(bucket)