from __future__ import annotations

from pathlib import Path

from aws_guardrails.validators import validate_file, validate_path


def write_file(path: Path, content: str) -> Path:
    path.write_text(content, encoding="utf-8")
    return path


def test_valid_iam_policy_passes(tmp_path: Path):
    policy = write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Sid": "AllowListBucket",
              "Effect": "Allow",
              "Action": ["s3:ListBucket"],
              "Resource": "arn:aws:s3:::example-bucket"
            }
          ]
        }
        """,
    )

    result = validate_file(policy, kind="iam")

    assert result.is_valid is True
    assert result.issues == ()


def test_invalid_iam_json_reports_line_and_column(tmp_path: Path):
    policy = write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Action": ["s3:GetObject"],
              "Resource": "arn:aws:s3:::example/*",
            }
          ]
        }
        """,
    )

    result = validate_file(policy, kind="iam")

    assert result.is_valid is False
    assert result.issues[0].code == "JSON001"
    assert result.issues[0].line is not None
    assert result.issues[0].column is not None


def test_duplicate_key_in_iam_policy_is_rejected(tmp_path: Path):
    policy = write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": [],
          "Statement": []
        }
        """,
    )

    result = validate_file(policy, kind="iam")

    assert result.is_valid is False
    assert result.issues[0].code == "JSON002"


def test_invalid_iam_statement_shape_is_reported(tmp_path: Path):
    policy = write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": {
            "Effect": "Permit",
            "Resource": "arn:aws:s3:::example-bucket"
          }
        }
        """,
    )

    result = validate_file(policy, kind="iam")

    assert result.is_valid is False
    codes = {issue.code for issue in result.issues}
    assert "IAM010" in codes
    assert "IAM011" in codes


def test_wildcard_allow_statement_fails_least_privilege(tmp_path: Path):
    policy = write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
          }
        }
        """,
    )

    result = validate_file(policy, kind="iam")

    assert result.is_valid is False
    assert {issue.code for issue in result.issues} == {"IAM100"}


def test_invalid_action_suggests_correct_service(tmp_path: Path):
    policy = write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": {
            "Effect": "Allow",
            "Action": "ec2:CreateRole",
            "Resource": "*"
          }
        }
        """,
    )

    result = validate_file(policy, kind="iam")

    assert result.is_valid is False
    issue = next(issue for issue in result.issues if issue.code == "IAM033")
    assert issue.suggestion == "Did you mean 'iam:CreateRole'?"


def test_invalid_action_typo_suggests_same_service_fix(tmp_path: Path):
    policy = write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": {
            "Effect": "Allow",
            "Action": "s3:GetObejct",
            "Resource": "arn:aws:s3:::example/*"
          }
        }
        """,
    )

    result = validate_file(policy, kind="iam")

    assert result.is_valid is False
    issue = next(issue for issue in result.issues if issue.code == "IAM033")
    assert issue.suggestion == "Did you mean 's3:GetObject'?"


def test_resource_star_not_warned_for_non_resource_scoped_action(tmp_path: Path):
    policy = write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": {
            "Effect": "Allow",
            "Action": "iam:ListUsers",
            "Resource": "*"
          }
        }
        """,
    )

    result = validate_file(policy, kind="iam")

    assert result.is_valid is True
    assert result.issues == ()


def test_passrole_without_passed_to_service_is_warning_only(tmp_path: Path):
    policy = write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": {
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "arn:aws:iam::123456789012:role/AppRole"
          }
        }
        """,
    )

    result = validate_file(policy, kind="iam")

    assert result.is_valid is True
    assert [(issue.code, issue.severity) for issue in result.issues] == [("IAM111", "WARNING")]


def test_valid_cloudformation_yaml_with_intrinsics_passes(tmp_path: Path):
    template = write_file(
        tmp_path / "template.yaml",
        """
        AWSTemplateFormatVersion: "2010-09-09"
        Description: Example stack
        Parameters:
          BucketName:
            Type: String
        Resources:
          ExampleBucket:
            Type: AWS::S3::Bucket
            Properties:
              BucketName: !Ref BucketName
        Outputs:
          BucketArn:
            Value: !Sub arn:aws:s3:::${ExampleBucket}
        """,
    )

    result = validate_file(template, kind="cloudformation")

    assert result.is_valid is True
    assert result.issues == ()


def test_duplicate_key_in_cloudformation_yaml_is_rejected(tmp_path: Path):
    template = write_file(
        tmp_path / "template.yaml",
        """
        Resources:
          ExampleBucket:
            Type: AWS::S3::Bucket
          ExampleBucket:
            Type: AWS::S3::Bucket
        """,
    )

    result = validate_file(template, kind="cloudformation")

    assert result.is_valid is False
    assert result.issues[0].code == "DOC002"
    assert result.issues[0].line == 5


def test_cloudformation_resource_requires_type(tmp_path: Path):
    template = write_file(
        tmp_path / "template.yaml",
        """
        Resources:
          ExampleBucket:
            Properties:
              BucketName: example
        """,
    )

    result = validate_file(template, kind="cloudformation")

    assert result.is_valid is False
    assert result.issues[0].code == "CFN010"


def test_cloudformation_sensitive_parameter_literal_default_fails(tmp_path: Path):
    template = write_file(
        tmp_path / "template.yaml",
        """
        Parameters:
          DbPassword:
            Type: String
            Default: super-secret-value
        """,
    )

    result = validate_file(template, kind="cloudformation")

    assert result.is_valid is False
    codes = {issue.code for issue in result.issues}
    assert "CFN101" in codes
    assert "CFN102" in codes


def test_cloudformation_identity_policy_rejects_principal(tmp_path: Path):
    template = write_file(
        tmp_path / "template.yaml",
        """
        Resources:
          ManagedPolicy:
            Type: AWS::IAM::ManagedPolicy
            Properties:
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: Allow
                    Principal: "*"
                    Action: s3:GetObject
                    Resource: arn:aws:s3:::example/*
        """,
    )

    result = validate_file(template, kind="cloudformation")

    assert result.is_valid is False
    assert "IAM020" in {issue.code for issue in result.issues}


def test_cloudformation_trust_policy_public_principal_fails(tmp_path: Path):
    template = write_file(
        tmp_path / "template.yaml",
        """
        Resources:
          AppRole:
            Type: AWS::IAM::Role
            Properties:
              AssumeRolePolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: Allow
                    Principal: "*"
                    Action: sts:AssumeRole
              Policies: []
        """,
    )

    result = validate_file(template, kind="cloudformation")

    assert result.is_valid is False
    assert "IAM025" in {issue.code for issue in result.issues}


def test_cloudformation_resource_policy_public_access_fails(tmp_path: Path):
    template = write_file(
        tmp_path / "template.yaml",
        """
        Resources:
          ExampleBucketPolicy:
            Type: AWS::S3::BucketPolicy
            Properties:
              Bucket: example-bucket
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: Allow
                    Principal: "*"
                    Action: s3:GetObject
                    Resource: arn:aws:s3:::example-bucket/*
        """,
    )

    result = validate_file(template, kind="cloudformation")

    assert result.is_valid is False
    assert "IAM109" in {issue.code for issue in result.issues}


def test_cloudformation_inline_policy_invalid_action_is_reported_with_fix(tmp_path: Path):
    template = write_file(
        tmp_path / "template.yaml",
        """
        Resources:
          AppPolicy:
            Type: AWS::IAM::ManagedPolicy
            Properties:
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: Allow
                    Action: ec2:CreateRole
                    Resource: "*"
        """,
    )

    result = validate_file(template, kind="cloudformation")

    assert result.is_valid is False
    issue = next(issue for issue in result.issues if issue.code == "IAM033")
    assert "Resources.AppPolicy.Properties.PolicyDocument.Statement[0].Action" in issue.message
    assert issue.suggestion == "Did you mean 'iam:CreateRole'?"


def test_auto_detection_walks_directory(tmp_path: Path):
    write_file(
        tmp_path / "policy.json",
        """
        {
          "Version": "2012-10-17",
          "Statement": {
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::example"
          }
        }
        """,
    )
    write_file(
        tmp_path / "template.yaml",
        """
        Resources:
          ExampleBucket:
            Type: AWS::S3::Bucket
        """,
    )

    results = validate_path(tmp_path)

    assert len(results) == 2
    assert all(result.is_valid for result in results)
