from typing import (
    cast,
)
from uuid import uuid4

from galaxy.config import GalaxyAppConfiguration
from galaxy.exceptions import (
    InsufficientPermissionsException,
    ItemAlreadyClaimedException,
    ItemMustBeClaimed,
    ObjectNotFound,
)
from galaxy.managers.landing import LandingRequestManager
from galaxy.managers.workflows import WorkflowContentsManager
from galaxy.model import (
    StoredWorkflow,
    Workflow,
)
from galaxy.schema.schema import (
    ClaimLandingPayload,
    CreateToolLandingRequestPayload,
    CreateWorkflowLandingRequestPayload,
    LandingRequestState,
    ToolLandingRequest,
    WorkflowLandingRequest,
)
from galaxy.structured_app import MinimalManagerApp
from galaxy.tool_util.parameters import (
    DataParameterModel,
    ToolParameterT,
)
from galaxy.workflow.trs_proxy import TrsProxy
from .base import BaseTestCase

TEST_TOOL_ID = "cat1"
TEST_TOOL_VERSION = "1.0.0"
TEST_STATE = {
    "input1": {
        "src": "url",
        "url": "https://raw.githubusercontent.com/galaxyproject/planemo/7be1bf5b3971a43eaa73f483125bfb8cabf1c440/tests/data/hello.txt",
        "ext": "txt",
    },
}
CLIENT_SECRET = "mycoolsecret"


class MockApp:

    @property
    def toolbox(self):
        return MockToolbox()


class MockToolbox:

    def get_tool(self, tool_id, tool_uuid, tool_version, user):
        return MockTool()


class MockTool:

    id = TEST_TOOL_ID

    @property
    def parameters(self) -> list[ToolParameterT]:
        return [DataParameterModel(type="data", name="input1")]


class TestLanding(BaseTestCase):

    def setUp(self):
        super().setUp()
        self.workflow_contents_manager = WorkflowContentsManager(self.app, self.app.trs_proxy)
        self.landing_manager = LandingRequestManager(
            self.trans.sa_session,
            self.app.security,
            self.workflow_contents_manager,
            cast(MinimalManagerApp, MockApp()),
            self.app.config,
        )
        self.trans.app.trs_proxy = TrsProxy(GalaxyAppConfiguration(override_tempdir=False))

    def test_tool_landing_requests_typical_flow(self):
        landing_request: ToolLandingRequest = self.landing_manager.create_tool_landing_request(self._tool_request)
        assert landing_request.state == LandingRequestState.UNCLAIMED
        assert landing_request.uuid is not None
        uuid = landing_request.uuid
        claim_payload = ClaimLandingPayload(client_secret=CLIENT_SECRET)
        landing_request = self.landing_manager.claim_tool_landing_request(self.trans, uuid, claim_payload)
        assert landing_request.state == LandingRequestState.CLAIMED
        assert landing_request.uuid == uuid
        landing_request = self.landing_manager.get_tool_landing_request(self.trans, uuid)
        assert landing_request.state == LandingRequestState.CLAIMED
        assert landing_request.uuid == uuid

    def test_tool_landing_requests_requires_matching_client_secret(self):
        landing_request: ToolLandingRequest = self.landing_manager.create_tool_landing_request(self._tool_request)
        uuid = landing_request.uuid
        claim_payload = ClaimLandingPayload(client_secret="wrongsecret")
        exception = None
        try:
            self.landing_manager.claim_tool_landing_request(self.trans, uuid, claim_payload)
        except InsufficientPermissionsException as e:
            exception = e
        assert exception is not None

    def test_tool_landing_requests_get_requires_claim(self):
        landing_request: ToolLandingRequest = self.landing_manager.create_tool_landing_request(self._tool_request)
        uuid = landing_request.uuid
        exception = None
        try:
            self.landing_manager.get_tool_landing_request(self.trans, uuid)
        except ItemMustBeClaimed as e:
            exception = e
        assert exception is not None

    def test_cannot_reclaim_tool_landing(self):
        landing_request: ToolLandingRequest = self.landing_manager.create_tool_landing_request(self._tool_request)
        assert landing_request.state == LandingRequestState.UNCLAIMED
        uuid = landing_request.uuid
        claim_payload = ClaimLandingPayload(client_secret=CLIENT_SECRET)
        landing_request = self.landing_manager.claim_tool_landing_request(self.trans, uuid, claim_payload)
        assert landing_request.state == LandingRequestState.CLAIMED
        exception = None
        try:
            self.landing_manager.claim_tool_landing_request(self.trans, uuid, claim_payload)
        except ItemAlreadyClaimedException as e:
            exception = e
        assert exception

    def test_get_tool_unknown_claim(self):
        exception = None
        try:
            self.landing_manager.get_tool_landing_request(self.trans, uuid4())
        except ObjectNotFound as e:
            exception = e
        assert exception

    def test_stored_workflow_landing_requests_typical_flow(self):
        landing_request: WorkflowLandingRequest = self.landing_manager.create_workflow_landing_request(
            self._stored_workflow_request
        )
        assert landing_request.state == LandingRequestState.UNCLAIMED
        assert landing_request.uuid is not None
        assert landing_request.workflow_target_type == "stored_workflow"
        uuid = landing_request.uuid
        claim_payload = ClaimLandingPayload(client_secret=CLIENT_SECRET)
        landing_request = self.landing_manager.claim_workflow_landing_request(self.trans, uuid, claim_payload)
        assert landing_request.state == LandingRequestState.CLAIMED
        assert landing_request.uuid == uuid
        assert landing_request.workflow_target_type == "stored_workflow"
        landing_request = self.landing_manager.get_workflow_landing_request(self.trans, uuid)
        assert landing_request.state == LandingRequestState.CLAIMED
        assert landing_request.uuid == uuid
        assert landing_request.workflow_target_type == "stored_workflow"

    def test_workflow_landing_requests_typical_flow(self):
        landing_request: WorkflowLandingRequest = self.landing_manager.create_workflow_landing_request(
            self._workflow_request
        )
        assert landing_request.state == LandingRequestState.UNCLAIMED
        assert landing_request.uuid is not None
        assert landing_request.workflow_target_type == "workflow"
        uuid = landing_request.uuid
        claim_payload = ClaimLandingPayload(client_secret=CLIENT_SECRET)
        landing_request = self.landing_manager.claim_workflow_landing_request(self.trans, uuid, claim_payload)
        assert landing_request.state == LandingRequestState.CLAIMED
        assert landing_request.uuid == uuid
        assert landing_request.workflow_target_type == "workflow"
        landing_request = self.landing_manager.get_workflow_landing_request(self.trans, uuid)
        assert landing_request.state == LandingRequestState.CLAIMED
        assert landing_request.uuid == uuid
        assert landing_request.workflow_target_type == "workflow"

    @property
    def _tool_request(self) -> CreateToolLandingRequestPayload:
        return CreateToolLandingRequestPayload(
            tool_id=TEST_TOOL_ID,
            tool_version=TEST_TOOL_VERSION,
            request_state=TEST_STATE.copy(),
            client_secret=CLIENT_SECRET,
        )

    @property
    def _stored_workflow_request(self) -> CreateWorkflowLandingRequestPayload:
        sa_session = self.app.model.context
        stored_workflow = StoredWorkflow()
        stored_workflow.user = self.trans.user
        sa_session.add(stored_workflow)
        sa_session.commit()

        return CreateWorkflowLandingRequestPayload(
            workflow_id=self.app.security.encode_id(stored_workflow.id),
            workflow_target_type="stored_workflow",
            request_state=TEST_STATE.copy(),
            client_secret=CLIENT_SECRET,
        )

    @property
    def _workflow_request(self) -> CreateWorkflowLandingRequestPayload:
        sa_session = self.app.model.context
        stored_workflow = StoredWorkflow()
        stored_workflow.user = self.trans.user
        workflow = Workflow()
        workflow.stored_workflow = stored_workflow
        sa_session.add(stored_workflow)
        sa_session.add(workflow)
        sa_session.commit()

        return CreateWorkflowLandingRequestPayload(
            workflow_id=self.app.security.encode_id(workflow.id),
            workflow_target_type="workflow",
            request_state=TEST_STATE.copy(),
            client_secret=CLIENT_SECRET,
        )
