# -*- coding: utf-8 -*-
#
# This file is part of REANA.
# Copyright (C) 2019 CERN.
#
# REANA is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

"""REANA-DB models tests."""

from uuid import uuid4

import pytest
import sqlalchemy
from mock import patch

from reana_db.models import (
    ALLOWED_WORKFLOW_STATUS_TRANSITIONS,
    AuditLog,
    AuditLogAction,
    User,
    UserTokenStatus,
    UserTokenType,
    Workflow,
    WorkflowStatus,
)


def test_workflow_run_number_assignment(db, session):
    """Test workflow run number assignment."""
    workflow_name = "workflow"
    owner_id = str(uuid4())
    first_workflow = Workflow(
        id_=str(uuid4()),
        name=workflow_name,
        owner_id=owner_id,
        reana_specification=[],
        type_="serial",
        logs="",
    )
    session.add(first_workflow)
    session.commit()
    assert first_workflow.run_number == 1
    second_workflow = Workflow(
        id_=str(uuid4()),
        name=workflow_name,
        owner_id=owner_id,
        reana_specification=[],
        type_="serial",
        logs="",
    )
    session.add(second_workflow)
    session.commit()
    assert second_workflow.run_number == 2
    first_workflow_restart = Workflow(
        id_=str(uuid4()),
        name=workflow_name,
        owner_id=owner_id,
        reana_specification=[],
        type_="serial",
        logs="",
        restart=True,
        run_number=first_workflow.run_number,
    )
    session.add(first_workflow_restart)
    session.commit()
    assert first_workflow_restart.run_number == 1.1
    first_workflow_second_restart = Workflow(
        id_=str(uuid4()),
        name=workflow_name,
        owner_id=owner_id,
        reana_specification=[],
        type_="serial",
        logs="",
        restart=True,
        run_number=first_workflow_restart.run_number,
    )
    session.add(first_workflow_second_restart)
    session.commit()
    assert first_workflow_second_restart.run_number == 1.2


@pytest.mark.parametrize(
    "from_status, to_status, can_transition",
    [
        (WorkflowStatus.created, WorkflowStatus.failed, False),
        (WorkflowStatus.created, WorkflowStatus.finished, False),
        (WorkflowStatus.created, WorkflowStatus.stopped, False),
        (WorkflowStatus.deleted, WorkflowStatus.created, False),
        (WorkflowStatus.deleted, WorkflowStatus.failed, False),
        (WorkflowStatus.deleted, WorkflowStatus.finished, False),
        (WorkflowStatus.deleted, WorkflowStatus.stopped, False),
        (WorkflowStatus.failed, WorkflowStatus.created, False),
        (WorkflowStatus.failed, WorkflowStatus.finished, False),
        (WorkflowStatus.failed, WorkflowStatus.stopped, False),
        (WorkflowStatus.finished, WorkflowStatus.created, False),
        (WorkflowStatus.finished, WorkflowStatus.failed, False),
        (WorkflowStatus.finished, WorkflowStatus.stopped, False),
        (WorkflowStatus.running, WorkflowStatus.created, False),
        (WorkflowStatus.running, WorkflowStatus.deleted, False),
        (WorkflowStatus.stopped, WorkflowStatus.created, False),
        (WorkflowStatus.stopped, WorkflowStatus.failed, False),
        (WorkflowStatus.stopped, WorkflowStatus.finished, False),
        (WorkflowStatus.stopped, WorkflowStatus.running, False),
    ]
    + [tuple + (True,) for tuple in ALLOWED_WORKFLOW_STATUS_TRANSITIONS],
)
def test_workflow_can_transition_to(
    db, session, from_status, to_status, can_transition
):
    """Test workflow run number assignment."""
    workflow_name = "test-workflow"
    owner_id = str(uuid4())
    workflow = Workflow(
        id_=str(uuid4()),
        name=workflow_name,
        owner_id=owner_id,
        reana_specification=[],
        type_="serial",
        logs="",
        status=from_status,
    )
    session.add(workflow)
    session.commit()
    assert workflow.can_transition_to(to_status) is can_transition


@pytest.mark.parametrize(
    "action, can_do",
    [
        (AuditLogAction.request_token, True),
        ("request_token", True),
        ("delete_database", False),
    ],
)
def test_audit_action(session, new_user, action, can_do):
    """Test audit log actions creation."""
    details = {"reason": "Use REANA."}

    def _audit_action():
        audited_action = new_user.log_action(action, details)
        return audited_action

    if can_do:
        audited_action = _audit_action()
        assert audited_action.action == getattr(
            AuditLogAction, getattr(action, "name", action)
        )
        assert audited_action.details == details
    else:
        with pytest.raises(sqlalchemy.exc.IntegrityError):
            _audit_action()


def test_access_token(db, session, new_user):
    """Test user access token use cases."""
    assert new_user.access_token
    assert new_user.access_token_status == UserTokenStatus.active.name
    assert new_user.tokens.count() == 1
    assert new_user.active_token.type_ == UserTokenType.reana

    # Assign second active access token
    with pytest.raises(Exception) as e:
        new_user.access_token = "new_token"
    assert "has already an active access token" in e.value.args[0]

    # Revoke token
    new_user.active_token.status = UserTokenStatus.revoked.name
    session.commit()
    assert not new_user.access_token
    assert not new_user.active_token
    assert new_user.access_token_status == UserTokenStatus.revoked.name

    # User requests token
    new_user.request_access_token()
    assert not new_user.access_token
    assert new_user.access_token_status == UserTokenStatus.requested.name

    # Tries to request again
    with pytest.raises(Exception) as e:
        new_user.request_access_token()
    assert "has already requested an access token" in e.value.args[0]

    # Grant new token
    new_user.access_token = "new_token"
    session.commit()
    assert new_user.access_token == "new_token"
    assert new_user.tokens.count() == 2

    # Status of most recent access token
    assert new_user.access_token_status == UserTokenStatus.active.name
