import pytest

from lightning.app.core.flow import LightningFlow
from lightning.app.core.work import LightningWork
from lightning.app.frontend.web import StaticWebFrontend
from lightning.app.utilities.layout import _collect_layout


class _MockApp:
    def __init__(self) -> None:
        self.frontends = {}


class FlowWithFrontend(LightningFlow):
    def configure_layout(self):
        return StaticWebFrontend(".")


class WorkWithFrontend(LightningWork):
    def run(self):
        pass

    def configure_layout(self):
        return StaticWebFrontend(".")


class FlowWithWorkWithFrontend(LightningFlow):
    def __init__(self):
        super().__init__()

        self.work = WorkWithFrontend()

    def configure_layout(self):
        return {"name": "work", "content": self.work}


class FlowWithUrl(LightningFlow):
    def configure_layout(self):
        return {"name": "test", "content": "https://test"}


class WorkWithUrl(LightningWork):
    def run(self):
        pass

    def configure_layout(self):
        return "https://test"


class FlowWithWorkWithUrl(LightningFlow):
    def __init__(self):
        super().__init__()

        self.work = WorkWithUrl()

    def configure_layout(self):
        return {"name": "test", "content": self.work}


@pytest.mark.parametrize(
    ("flow", "expected_layout", "expected_frontends"),
    [
        (FlowWithFrontend, {}, [("root", StaticWebFrontend)]),
        (FlowWithWorkWithFrontend, {}, [("root", StaticWebFrontend)]),
        (FlowWithUrl, [{"name": "test", "content": "https://test", "target": "https://test"}], []),
        (FlowWithWorkWithUrl, [{"name": "test", "content": "https://test", "target": "https://test"}], []),
    ],
)
def test_collect_layout(flow, expected_layout, expected_frontends):
    app = _MockApp()
    flow = flow()
    layout = _collect_layout(app, flow)

    assert layout == expected_layout
    assert set(app.frontends.keys()) == {key for key, _ in expected_frontends}
    for key, frontend_type in expected_frontends:
        assert isinstance(app.frontends[key], frontend_type)


class FlowWithBadLayout(LightningFlow):
    def configure_layout(self):
        return 100


class FlowWithBadLayoutDict(LightningFlow):
    def configure_layout(self):
        return {"this_key_should_not_be_here": "http://appurl"}


class FlowWithBadContent(LightningFlow):
    def configure_layout(self):
        return {"content": 100}


class WorkWithBadLayout(LightningWork):
    def run(self):
        pass

    def configure_layout(self):
        return 100


class FlowWithWorkWithBadLayout(LightningFlow):
    def __init__(self):
        super().__init__()

        self.work = WorkWithBadLayout()

    def configure_layout(self):
        return {"name": "test", "content": self.work}


class FlowWithMultipleWorksWithFrontends(LightningFlow):
    def __init__(self):
        super().__init__()

        self.work1 = WorkWithFrontend()
        self.work2 = WorkWithFrontend()

    def configure_layout(self):
        return [{"name": "test1", "content": self.work1}, {"name": "test2", "content": self.work2}]


@pytest.mark.parametrize(
    ("flow", "error_type", "match"),
    [
        (FlowWithBadLayout, TypeError, "is an unsupported layout format"),
        (FlowWithBadLayoutDict, ValueError, "missing a key 'content'."),
        (FlowWithBadContent, ValueError, "contains an unsupported entry."),
        (FlowWithWorkWithBadLayout, TypeError, "is of an unsupported type."),
        (
            FlowWithMultipleWorksWithFrontends,
            TypeError,
            "The tab containing a `WorkWithFrontend` must be the only tab",
        ),
    ],
)
def test_collect_layout_errors(flow, error_type, match):
    app = _MockApp()
    flow = flow()

    with pytest.raises(error_type, match=match):
        _collect_layout(app, flow)
