from __future__ import annotations

from unittest.mock import patch
from uuid import uuid4

from flask import Flask
from sqlalchemy.orm import Session

from models.model import App, RecommendedApp, Site
from services.recommend_app.database.database_retrieval import DatabaseRecommendAppRetrieval


def _create_app(db_session: Session, *, tenant_id: str, is_public: bool = True) -> App:
    app = App(
        tenant_id=tenant_id,
        name=f"app-{uuid4()}",
        mode="chat",
        enable_site=True,
        enable_api=True,
        is_public=is_public,
    )
    app.id = str(uuid4())
    db_session.add(app)
    db_session.commit()
    return app


def _create_site(db_session: Session, *, app_id: str) -> Site:
    site = Site(
        app_id=app_id,
        title=f"site-{uuid4()}",
        default_language="en-US",
        customize_token_strategy="not_allow",
        description="desc",
        copyright="copy",
        privacy_policy="pp",
        custom_disclaimer="cd",
    )
    site.id = str(uuid4())
    db_session.add(site)
    db_session.commit()
    return site


def _create_recommended_app(
    db_session,
    *,
    app_id: str,
    category: str = "chat",
    categories: list[str] | None = None,
    language: str = "en-US",
    is_listed: bool = True,
    is_learn_dify: bool = False,
    position: int = 1,
) -> RecommendedApp:
    rec = RecommendedApp(
        app_id=app_id,
        description={"en-US": "test"},
        copyright="copy",
        privacy_policy="pp",
        category=category,
        categories=[category] if categories is None else categories,
        language=language,
        is_listed=is_listed,
        is_learn_dify=is_learn_dify,
        position=position,
    )
    rec.id = str(uuid4())
    db_session.add(rec)
    db_session.commit()
    return rec


class TestFetchRecommendedAppsFromDb:
    def test_returns_apps_and_sorted_categories(
        self, flask_app_with_containers: Flask, db_session_with_containers: Session
    ):
        tenant_id = str(uuid4())
        app1 = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_site(db_session_with_containers, app_id=app1.id)
        _create_recommended_app(db_session_with_containers, app_id=app1.id, category="writing")

        app2 = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_site(db_session_with_containers, app_id=app2.id)
        _create_recommended_app(db_session_with_containers, app_id=app2.id, category="assistant")

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_recommended_apps_from_db(
            "en-US", session=db_session_with_containers
        )

        app_ids = {r["app_id"] for r in result["recommended_apps"]}
        assert app1.id in app_ids
        assert app2.id in app_ids
        assert "assistant" in result["categories"]
        assert "writing" in result["categories"]

    def test_returns_multiple_categories_for_one_app(
        self, flask_app_with_containers: Flask, db_session_with_containers: Session
    ):
        tenant_id = str(uuid4())
        created_app = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_site(db_session_with_containers, app_id=created_app.id)
        _create_recommended_app(
            db_session_with_containers,
            app_id=created_app.id,
            category="writing",
            categories=["writing", "assistant"],
        )

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_recommended_apps_from_db(
            "en-US", session=db_session_with_containers
        )

        recommended_app = next(item for item in result["recommended_apps"] if item["app_id"] == created_app.id)
        assert recommended_app["categories"] == ["writing", "assistant"]
        assert "writing" in result["categories"]
        assert "assistant" in result["categories"]

    def test_ignores_legacy_category_when_categories_are_empty(
        self,
        flask_app_with_containers: Flask,
        db_session_with_containers: Session,
    ):
        legacy_category = f"legacy-empty-{uuid4()}"
        tenant_id = str(uuid4())
        created_app = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_site(db_session_with_containers, app_id=created_app.id)
        _create_recommended_app(
            db_session_with_containers,
            app_id=created_app.id,
            category=legacy_category,
            categories=[],
        )

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_recommended_apps_from_db(
            "en-US", session=db_session_with_containers
        )

        recommended_app = next(item for item in result["recommended_apps"] if item["app_id"] == created_app.id)
        assert "category" not in recommended_app
        assert recommended_app["categories"] == []
        assert legacy_category not in result["categories"]

    def test_falls_back_to_default_language_when_empty(
        self, flask_app_with_containers: Flask, db_session_with_containers: Session
    ):
        tenant_id = str(uuid4())
        app1 = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_site(db_session_with_containers, app_id=app1.id)
        _create_recommended_app(db_session_with_containers, app_id=app1.id, language="en-US")

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_recommended_apps_from_db(
            "fr-FR", session=db_session_with_containers
        )

        app_ids = {r["app_id"] for r in result["recommended_apps"]}
        assert app1.id in app_ids

    def test_skips_non_public_apps(self, flask_app_with_containers: Flask, db_session_with_containers: Session):
        tenant_id = str(uuid4())
        app1 = _create_app(db_session_with_containers, tenant_id=tenant_id, is_public=False)
        _create_site(db_session_with_containers, app_id=app1.id)
        _create_recommended_app(db_session_with_containers, app_id=app1.id)

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_recommended_apps_from_db(
            "en-US", session=db_session_with_containers
        )

        app_ids = {r["app_id"] for r in result["recommended_apps"]}
        assert app1.id not in app_ids

    def test_skips_apps_without_site(self, flask_app_with_containers: Flask, db_session_with_containers: Session):
        tenant_id = str(uuid4())
        app1 = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_recommended_app(db_session_with_containers, app_id=app1.id)

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_recommended_apps_from_db(
            "en-US", session=db_session_with_containers
        )

        app_ids = {r["app_id"] for r in result["recommended_apps"]}
        assert app1.id not in app_ids

    def test_fetch_learn_dify_apps_uses_flag_not_categories(
        self,
        flask_app_with_containers,
        db_session_with_containers: Session,
    ):
        tenant_id = str(uuid4())
        learn_dify_app = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_site(db_session_with_containers, app_id=learn_dify_app.id)
        _create_recommended_app(
            db_session_with_containers,
            app_id=learn_dify_app.id,
            category="workflow",
            categories=["Workflow"],
            is_learn_dify=True,
        )

        category_only_app = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_site(db_session_with_containers, app_id=category_only_app.id)
        _create_recommended_app(
            db_session_with_containers,
            app_id=category_only_app.id,
            category="Learn Dify",
            categories=["Learn Dify"],
            is_learn_dify=False,
        )

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_learn_dify_apps_from_db(
            "en-US", session=db_session_with_containers
        )

        app_ids = {r["app_id"] for r in result["recommended_apps"]}
        assert learn_dify_app.id in app_ids
        assert category_only_app.id not in app_ids
        recommended_app = next(item for item in result["recommended_apps"] if item["app_id"] == learn_dify_app.id)
        assert recommended_app["categories"] == ["Workflow"]

    def test_fetch_learn_dify_apps_falls_back_to_default_language(
        self,
        flask_app_with_containers,
        db_session_with_containers: Session,
    ):
        tenant_id = str(uuid4())
        learn_dify_app = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_site(db_session_with_containers, app_id=learn_dify_app.id)
        _create_recommended_app(
            db_session_with_containers,
            app_id=learn_dify_app.id,
            categories=["Workflow"],
            is_learn_dify=True,
            language="en-US",
        )

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_learn_dify_apps_from_db(
            "fr-FR", session=db_session_with_containers
        )

        app_ids = {r["app_id"] for r in result["recommended_apps"]}
        assert learn_dify_app.id in app_ids


class TestFetchRecommendedAppDetailFromDb:
    def test_returns_none_when_not_listed(self, flask_app_with_containers: Flask, db_session_with_containers: Session):
        result = DatabaseRecommendAppRetrieval.fetch_recommended_app_detail_from_db(
            str(uuid4()), session=db_session_with_containers
        )

        assert result is None

    def test_returns_none_when_app_not_public(
        self, flask_app_with_containers: Flask, db_session_with_containers: Session
    ):
        tenant_id = str(uuid4())
        app1 = _create_app(db_session_with_containers, tenant_id=tenant_id, is_public=False)
        _create_recommended_app(db_session_with_containers, app_id=app1.id)

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_recommended_app_detail_from_db(
            app1.id, session=db_session_with_containers
        )

        assert result is None

    @patch("services.recommend_app.database.database_retrieval.AppDslService")
    def test_returns_detail_on_success(
        self, mock_dsl, flask_app_with_containers: Flask, db_session_with_containers: Session
    ):
        tenant_id = str(uuid4())
        app1 = _create_app(db_session_with_containers, tenant_id=tenant_id)
        _create_site(db_session_with_containers, app_id=app1.id)
        _create_recommended_app(db_session_with_containers, app_id=app1.id)
        mock_dsl.export_dsl.return_value = "exported_yaml"

        db_session_with_containers.expire_all()

        result = DatabaseRecommendAppRetrieval.fetch_recommended_app_detail_from_db(
            app1.id, session=db_session_with_containers
        )

        assert result is not None
        assert result["id"] == app1.id
        assert result["export_data"] == "exported_yaml"
