import boto3
import mock
from lambda_fn import get_version, monitor_stage
from lambda_fn import SUCCESS, FAILED, TIMEOUT
from moto import mock_s3

REGION = "us-east-1"
PIPELINE = "pipeline"
STAGE = "Test"
ACTION = "TestAction"
TIME = 0
CLIENT = boto3.client("codepipeline", region_name=REGION)

successful_stage = {"status": "Succeeded",
                    "revision": "0",
                    "name": ACTION,
                    "error": {},
                    "latest": 1}

failed_stage = {"status": "Failed",
                "revision": "1",
                "name": ACTION,
                "error": {"type": "failure",
                        "message": "myfailure"},
                "latest": 1}

in_progress_stage = {"status": "InProgress",
                   "revision": "", # in-progress stages have no revision id
                   "name": ACTION,
                   "error": {},
                   "latest": 1}

successful_ver_stage = {"status": "Succeeded",
                  "revision": "1",
                  "name": ACTION,
                  "error": {},
                  "latest": 1}

failed_stage_no_ver = {"status": "Failed",
                "revision": "",
                "name": ACTION,
                "error": {"type": "failure",
                        "message": "myfailure"},
                "latest": 1}

successful_latest_stage = {"status": "Succeeded",
                  "revision": "1",
                  "name": ACTION,
                  "error": {},
                  "latest": 3}

class MockLambdaContext():
    def __init__(self, timeout=5000):
        self.timeout = timeout

    def get_remaining_time_in_millis(self):
        return self.timeout

@mock_s3
def test_get_version():
    """
    Verify that this method retrieves the 'VersionId' from the  S3 object
    """
    s3 = boto3.client("s3", region_name=REGION)

    test_bucket = "test_bucket"
    test_key = "test_key"

    config = {
        "Status": "Enabled",
    }

    s3.create_bucket(Bucket=test_bucket)
    s3.put_bucket_versioning(Bucket=test_bucket, VersioningConfiguration=config)
    s3.put_object(Bucket=test_bucket, Key=test_key, Body="meh")

    version = get_version(test_bucket, test_key, REGION)

    print version
    assert version == '0'

    s3.put_object(Bucket=test_bucket, Key=test_key, Body="meh")
    version = get_version(test_bucket, test_key, REGION)

    print version
    assert version == '1'

@mock.patch("lambda_fn.get_stage_metadata", return_value=[successful_stage])
def test_monitor_stage_success(mock_fn):
    version = "0"
    result, md = monitor_stage(MockLambdaContext(), CLIENT, PIPELINE, STAGE, version, TIME)

    assert result == SUCCESS
    assert md['latest'] == 1
    assert mock_fn.call_count == 1

@mock.patch("lambda_fn.get_stage_metadata", return_value=[failed_stage])
def test_monitor_stage_failure(mock_fn):
    version = "1"
    result, md = monitor_stage(MockLambdaContext(), CLIENT, PIPELINE, STAGE, version, TIME)

    assert result == FAILED
    assert md["type"] == "failure"
    assert md["message"] == "myfailure"
    assert mock_fn.call_count == 1

@mock.patch("lambda_fn.get_stage_metadata", return_value=[in_progress_stage])
def test_monitor_stage_timeout(mock_fn):
    version = "0"
    result, md = monitor_stage(MockLambdaContext(), CLIENT, PIPELINE, STAGE, version, TIME)

    assert result == TIMEOUT
    assert md == {}
    assert mock_fn.call_count == 1

@mock.patch("lambda_fn.WAIT_PERIOD_S", returns=1) # mock wait val from 5 -> 1 sec
@mock.patch("lambda_fn.get_stage_metadata", side_effect=[[in_progress_stage], [successful_stage]])
def test_monitor_stage_wait_for_success(mock_fn, mock_val):
    version = "0"
    result, md = monitor_stage(MockLambdaContext(timeout=11000), CLIENT, PIPELINE, STAGE, version, TIME)

    assert result == SUCCESS
    assert md['latest'] == 1
    assert mock_fn.call_count == 2

@mock.patch("lambda_fn.WAIT_PERIOD_S", returns=1) # mock wait val from 5 -> 1 sec
@mock.patch("lambda_fn.get_stage_metadata", side_effect=[[successful_ver_stage, in_progress_stage], [successful_ver_stage, successful_ver_stage]])
def test_monitor_stage_wait_for_version_update(mock_fn, mock_val):
    version = "1"
    result, md = monitor_stage(MockLambdaContext(timeout=11000), CLIENT, PIPELINE, STAGE, version, TIME)

    assert result == SUCCESS
    assert md['latest'] == 1
    assert mock_fn.call_count == 2

@mock.patch("lambda_fn.WAIT_PERIOD_S", returns=1) # mock wait val from 5 -> 1 sec
@mock.patch("lambda_fn.get_stage_metadata", side_effect=[[failed_stage_no_ver], [successful_latest_stage]])
def test_stale_failure(mock_fn, mock_val):
    version = "1"
    result, md = monitor_stage(MockLambdaContext(timeout=11000), CLIENT, PIPELINE, STAGE, version, 2)

    assert result == SUCCESS
    assert md['latest'] == 3
    assert mock_fn.call_count == 2
