"""Tests.tests.unit.domain.entities.test_dropout_analysis_session
Where: Automated test module.
What: Contains test cases verifying project behaviour.
Why: Ensures regressions are caught and documented.
"""

from noveler.domain.value_objects.project_time import ProjectTimezone, project_now

"""離脱率分析セッションのユニットテスト

TDD原則に基づき、実装前にテストを作成。
"""

from datetime import date

import pytest

from noveler.domain.entities.dropout_analysis_session import (
    AnalysisStatus,
    CriticalEpisode,
    DropoutAnalysisResult,
    DropoutAnalysisSession,
    DropoutRate,
    EpisodeMetrics,
)

# JSTタイムゾーン
JST = ProjectTimezone.jst().timezone


@pytest.mark.spec("SPEC-QUALITY-014")
class TestDropoutAnalysisSession:
    """離脱率分析セッションのテスト"""

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-CREATE_SESSION")
    def test_create_session(self) -> None:
        """分析セッションを作成できることを確認"""
        # Given
        session_id = "test-session-001"
        project_id = "test-project"
        ncode = "n1234kr"

        # When
        session = DropoutAnalysisSession(session_id=session_id, project_id=project_id, ncode=ncode)

        # Then
        assert session.session_id == session_id
        assert session.project_id == project_id
        assert session.ncode == ncode
        assert session.status == AnalysisStatus.PENDING

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-CALCULATE_DROPOUT_RA")
    def test_calculate_dropout_rate_from_access_data(self) -> None:
        """エピソード間の離脱率を正しく計算できることを確認"""
        # Given
        session_id = "test-session-001"
        project_id = "test-project"
        session = DropoutAnalysisSession(session_id=session_id, project_id=project_id, ncode="n1234kr")

        # Add episode metrics for dropout calculation
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=1, episode_title="第1話", page_views=1000, unique_users=100, access_date=date.today()
            )
        )
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=2, episode_title="第2話", page_views=800, unique_users=80, access_date=date.today()
            )
        )
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=3, episode_title="第3話", page_views=600, unique_users=60, access_date=date.today()
            )
        )

        # When
        result = session.analyze_dropout_rates(None)  # access_data parameter is ignored

        # Then
        assert result is not None
        assert len(result) == 2  # 1→2, 2→3

        # 第1話から第2話の離脱率: (1000-800)/1000 = 20%
        dropout_1_2 = result[0]
        assert dropout_1_2.episode_number == 2
        assert dropout_1_2.dropout_rate.value == pytest.approx(20.0, 0.1)

        # 第2話から第3話の離脱率: (800-600)/800 = 25%
        dropout_2_3 = result[1]
        assert dropout_2_3.episode_number == 3
        assert dropout_2_3.dropout_rate.value == pytest.approx(25.0, 0.1)

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-CALCULATE_AVERAGE_DR")
    def test_calculate_average_dropout_rate(self) -> None:
        """平均離脱率が正しく計算されることを確認"""
        # Given
        session_id = "test-session-001"
        project_id = "test-project"
        session = DropoutAnalysisSession(session_id=session_id, project_id=project_id, ncode="n1234kr")

        # Add episode metrics to calculate dropout rates
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=1, episode_title="第1話", page_views=1000, unique_users=100, access_date=date.today()
            )
        )
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=2, episode_title="第2話", page_views=800, unique_users=80, access_date=date.today()
            )
        )  # 20% dropout
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=3, episode_title="第3話", page_views=600, unique_users=60, access_date=date.today()
            )
        )  # 25% dropout
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=4, episode_title="第4話", page_views=500, unique_users=50, access_date=date.today()
            )
        )  # 16.7% dropout

        # When
        average_rate = session.calculate_average_dropout_rate()

        # Then
        # 平均: (20 + 25 + 16.7) / 3 ≈ 20.6%
        assert average_rate.value == pytest.approx(20.6, 0.1)

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-IDENTIFY_RISKY_EPISO")
    def test_identify_risky_episodes(self) -> None:
        """高い離脱率のエピソードを特定できることを確認"""
        # Given
        session_id = "test-session-001"
        project_id = "test-project"
        session = DropoutAnalysisSession(session_id=session_id, project_id=project_id, ncode="n1234kr")

        # Add episode metrics to calculate dropout rates
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=1, episode_title="第1話", page_views=1000, unique_users=100, access_date=date.today()
            )
        )
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=2, episode_title="第2話", page_views=900, unique_users=90, access_date=date.today()
            )
        )  # 10% dropout
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=3, episode_title="第3話", page_views=600, unique_users=60, access_date=date.today()
            )
        )  # 33.3% dropout - 危険!
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=4, episode_title="第4話", page_views=550, unique_users=55, access_date=date.today()
            )
        )  # 8.3% dropout

        # When
        critical_episodes = session.identify_critical_episodes(threshold=20.0)

        # Then
        assert len(critical_episodes) == 1
        assert critical_episodes[0].episode_number == 3
        assert critical_episodes[0].dropout_rate.value > 30.0

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-PROCESS_EMPTY_ACCESS")
    def test_process_empty_access_data(self) -> None:
        """空のアクセスデータでもエラーにならないことを確認"""
        # Given
        session_id = "test-session-001"
        project_id = "test-project"
        session = DropoutAnalysisSession(session_id=session_id, project_id=project_id, ncode="n1234kr")

        # When - no episode metrics added
        result = session.analyze_dropout_rates(None)

        # Then
        assert result is not None
        assert len(result) == 0
        average_rate = session.calculate_average_dropout_rate()
        assert average_rate.value == 0.0

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-SINGLE_EPISODE_ONLY_")
    def test_single_episode_only_data(self) -> None:
        """エピソードが1つしかない場合の処理を確認"""
        # Given
        session_id = "test-session-001"
        project_id = "test-project"
        session = DropoutAnalysisSession(session_id=session_id, project_id=project_id, ncode="n1234kr")

        # Add only one episode
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=1, episode_title="第1話", page_views=1000, unique_users=100, access_date=date.today()
            )
        )

        # When
        result = session.analyze_dropout_rates(None)

        # Then
        assert result is not None
        assert len(result) == 0  # No dropout can be calculated with only one episode
        average_rate = session.calculate_average_dropout_rate()
        assert average_rate.value == 0.0

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-TIME_SERIES_DATA_ANA")
    def test_time_series_data_analysis(self) -> None:
        """時系列でのアクセスデータ分析を確認"""
        # Given
        session_id = "test-session-001"
        project_id = "test-project"
        session = DropoutAnalysisSession(session_id=session_id, project_id=project_id, ncode="n1234kr")

        # 異なる日付のアクセスデータ(実際の実装では内部のepisode_metricsを使用)
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=1, episode_title="第1話", page_views=1100, unique_users=110, access_date=date(2024, 1, 2)
            )
        )
        session.add_episode_metrics(
            EpisodeMetrics(
                episode_number=2, episode_title="第2話", page_views=850, unique_users=85, access_date=date(2024, 1, 2)
            )
        )

        # When
        result = session.analyze_dropout_rates(None)

        # Then
        assert result is not None
        # 1100 -> 850 = 22.7% dropout
        if result:
            dropout = result[0]
            assert dropout.episode_number == 2
            assert dropout.dropout_rate.value == pytest.approx(22.7, 0.1)
        # Note: This test may need adjustment based on actual implementation
        # as the current implementation doesn't have analyze_dropout_rates_by_date method


@pytest.mark.spec("SPEC-QUALITY-014")
class TestDropoutAnalysisResult:
    """離脱率分析結果のテスト"""

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-CREATE_RESULT_AND_BA")
    def test_create_result_and_basic_properties(self) -> None:
        """分析結果の作成と基本的なプロパティを確認"""
        # Given
        critical_episodes = [
            CriticalEpisode(
                episode_number=2,
                episode_title="第2話",
                dropout_rate=DropoutRate(20.0),
                current_pv=800,
                previous_pv=1000,
                priority="medium",
            ),
            CriticalEpisode(
                episode_number=3,
                episode_title="第3話",
                dropout_rate=DropoutRate(15.0),
                current_pv=680,
                previous_pv=800,
                priority="medium",
            ),
        ]

        # When
        result = DropoutAnalysisResult(
            session_id="test-session-001",
            project_id="test-project",
            ncode="n1234kr",
            status=AnalysisStatus.COMPLETED,
            created_at=project_now().datetime,
            completed_at=project_now().datetime,
            total_episodes=2,
            average_dropout_rate=DropoutRate(17.5),
            critical_episodes=critical_episodes,
            recommendations=["Recommendation 1", "Recommendation 2"],
        )

        # Then
        assert len(result.critical_episodes) == 2
        assert result.average_dropout_rate.value == 17.5
        assert result.total_episodes == 2

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-GET_SPECIFIC_EPISODE")
    def test_get_specific_episode_dropout_info(self) -> None:
        """特定のエピソードの離脱情報を取得できることを確認"""
        # Given
        critical_episode = CriticalEpisode(
            episode_number=5,
            episode_title="第5話",
            dropout_rate=DropoutRate(30.0),
            current_pv=700,
            previous_pv=1000,
            priority="high",
        )

        result = DropoutAnalysisResult(
            session_id="test-session-001",
            project_id="test-project",
            ncode="n1234kr",
            status=AnalysisStatus.COMPLETED,
            created_at=project_now().datetime,
            completed_at=project_now().datetime,
            total_episodes=1,
            average_dropout_rate=DropoutRate(30.0),
            critical_episodes=[critical_episode],
            recommendations=["Fix episode 5"],
        )

        # When
        info = next((ep for ep in result.critical_episodes if ep.episode_number == 5), None)

        # Then
        assert info is not None
        assert info.episode_number == 5
        assert info.dropout_rate.value == 30.0

    @pytest.mark.spec("SPEC-DROPOUT_ANALYSIS_SESSION-GET_NONEXISTENT_EPIS")
    def test_get_nonexistent_episode_info(self) -> None:
        """存在しないエピソードの情報取得時はNoneを返すことを確認"""
        # Given
        result = DropoutAnalysisResult(
            session_id="test-session-001",
            project_id="test-project",
            ncode="n1234kr",
            status=AnalysisStatus.COMPLETED,
            created_at=project_now().datetime,
            completed_at=project_now().datetime,
            total_episodes=0,
            average_dropout_rate=DropoutRate(0.0),
            critical_episodes=[],
            recommendations=[],
        )

        # When
        info = next((ep for ep in result.critical_episodes if ep.episode_number == 999), None)

        # Then
        assert info is None
