"""conversion_tracker.py 단위 테스트.

TDD RED→GREEN 방식으로 작성된 테스트.
모든 테스트 데이터는 명시적으로 "샘플 데이터"로 표기됨.
"""

import io
import json
import sys
import os
import pytest

# 테스트 대상 모듈 경로 추가
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from conversion_tracker import (
    calculate_funnel,
    calculate_dropoff,
    analyze_ai_sources,
    generate_insights,
    parse_csv,
    build_report,
)


# ---------------------------------------------------------------------------
# 샘플 데이터 (명시적 표기)
# ---------------------------------------------------------------------------

SAMPLE_STAGE_DATA = [
    # (stage, users)
    ("AI 검색 도착", 500),
    ("콘텐츠 소비", 350),
    ("인터랙션", 120),
    ("관심", 60),
    ("전환", 25),
    ("재방문", 10),
    ("추천", 3),
]

SAMPLE_SOURCE_DATA = [
    # (stage, users, source)
    ("AI 검색 도착", 180, "ChatGPT"),
    ("AI 검색 도착", 120, "Perplexity"),
    ("AI 검색 도착", 200, "Gemini"),
    ("전환", 12, "ChatGPT"),
    ("전환", 8, "Perplexity"),
    ("전환", 5, "Gemini"),
]

SAMPLE_CSV_NO_SOURCE = """stage,users
AI 검색 도착,500
콘텐츠 소비,350
인터랙션,120
관심,60
전환,25
재방문,10
추천,3
"""

SAMPLE_CSV_WITH_SOURCE = """stage,users,source
AI 검색 도착,180,ChatGPT
AI 검색 도착,120,Perplexity
AI 검색 도착,200,Gemini
전환,12,ChatGPT
전환,8,Perplexity
전환,5,Gemini
"""


# ---------------------------------------------------------------------------
# 1. CSV 파싱 테스트
# ---------------------------------------------------------------------------


class TestParseCsv:
    def test_parse_csv_no_source_column(self):
        """소스 컬럼 없는 CSV 파싱 — 샘플 데이터."""
        rows = parse_csv(io.StringIO(SAMPLE_CSV_NO_SOURCE))
        assert len(rows) == 7
        assert rows[0]["stage"] == "AI 검색 도착"
        assert rows[0]["users"] == 500
        assert rows[0].get("source") is None

    def test_parse_csv_with_source_column(self):
        """소스 컬럼 있는 CSV 파싱 — 샘플 데이터."""
        rows = parse_csv(io.StringIO(SAMPLE_CSV_WITH_SOURCE))
        assert len(rows) == 6
        assert rows[0]["source"] == "ChatGPT"
        assert rows[0]["users"] == 180

    def test_parse_csv_users_are_integers(self):
        """users 값이 정수로 파싱되는지 확인 — 샘플 데이터."""
        rows = parse_csv(io.StringIO(SAMPLE_CSV_NO_SOURCE))
        for row in rows:
            assert isinstance(row["users"], int)

    def test_parse_csv_empty_file(self):
        """빈 CSV 파일 엣지 케이스."""
        rows = parse_csv(io.StringIO("stage,users\n"))
        assert rows == []


# ---------------------------------------------------------------------------
# 2. 퍼널 분석 테스트
# ---------------------------------------------------------------------------


class TestCalculateFunnel:
    def test_funnel_first_stage_rate_100(self):
        """첫 번째 단계는 rate=100% — 샘플 데이터."""
        funnel = calculate_funnel(SAMPLE_STAGE_DATA)
        assert funnel[0]["rate"] == "100%"
        assert "dropoff" not in funnel[0]

    def test_funnel_stages_count(self):
        """7단계 퍼널 — 샘플 데이터."""
        funnel = calculate_funnel(SAMPLE_STAGE_DATA)
        assert len(funnel) == 7

    def test_funnel_users_preserved(self):
        """users 값 보존 — 샘플 데이터."""
        funnel = calculate_funnel(SAMPLE_STAGE_DATA)
        assert funnel[0]["users"] == 500
        assert funnel[4]["users"] == 25

    def test_funnel_stage_names(self):
        """stage 이름 보존 — 샘플 데이터."""
        funnel = calculate_funnel(SAMPLE_STAGE_DATA)
        assert funnel[0]["stage"] == "AI 검색 도착"
        assert funnel[4]["stage"] == "전환"

    def test_funnel_second_stage_rate(self):
        """두 번째 단계 rate = 350/500*100 = 70% — 샘플 데이터."""
        funnel = calculate_funnel(SAMPLE_STAGE_DATA)
        assert funnel[1]["rate"] == "70.0%"

    def test_funnel_all_have_dropoff_except_first(self):
        """첫 번째 제외 모든 단계에 dropoff 존재 — 샘플 데이터."""
        funnel = calculate_funnel(SAMPLE_STAGE_DATA)
        for stage in funnel[1:]:
            assert "dropoff" in stage

    def test_funnel_empty_data(self):
        """빈 데이터 엣지 케이스."""
        funnel = calculate_funnel([])
        assert funnel == []


# ---------------------------------------------------------------------------
# 3. 드롭오프율 계산 테스트
# ---------------------------------------------------------------------------


class TestCalculateDropoff:
    def test_dropoff_basic(self):
        """기본 드롭오프율: (500-350)/500*100 = 30% — 샘플 데이터."""
        result = calculate_dropoff(500, 350)
        assert result == pytest.approx(30.0)

    def test_dropoff_zero_previous(self):
        """이전 단계 users=0 엣지 케이스 — ZeroDivisionError 방지."""
        result = calculate_dropoff(0, 100)
        assert result == 0.0

    def test_dropoff_no_dropoff(self):
        """드롭오프 없는 경우 (이전=현재): 0% — 샘플 데이터."""
        result = calculate_dropoff(100, 100)
        assert result == pytest.approx(0.0)

    def test_dropoff_full_dropoff(self):
        """전체 이탈: 100% — 샘플 데이터."""
        result = calculate_dropoff(100, 0)
        assert result == pytest.approx(100.0)

    def test_dropoff_interaction_to_conversion(self):
        """인터랙션→관심 드롭오프: (120-60)/120*100 = 50% — 샘플 데이터."""
        result = calculate_dropoff(120, 60)
        assert result == pytest.approx(50.0)


# ---------------------------------------------------------------------------
# 4. AI 소스별 분석 테스트
# ---------------------------------------------------------------------------


class TestAnalyzeAiSources:
    def test_sources_present(self):
        """샘플 소스 데이터에 ChatGPT, Perplexity, Gemini 포함 — 샘플 데이터."""
        result = analyze_ai_sources(SAMPLE_SOURCE_DATA)
        assert "ChatGPT" in result
        assert "Perplexity" in result
        assert "Gemini" in result

    def test_arrivals_correct(self):
        """ChatGPT arrivals=180 — 샘플 데이터."""
        result = analyze_ai_sources(SAMPLE_SOURCE_DATA)
        assert result["ChatGPT"]["arrivals"] == 180

    def test_conversions_correct(self):
        """ChatGPT conversions=12 — 샘플 데이터."""
        result = analyze_ai_sources(SAMPLE_SOURCE_DATA)
        assert result["ChatGPT"]["conversions"] == 12

    def test_cvr_calculation(self):
        """ChatGPT CVR = 12/180*100 = 6.67% — 샘플 데이터."""
        result = analyze_ai_sources(SAMPLE_SOURCE_DATA)
        cvr_val = float(result["ChatGPT"]["cvr"].replace("%", ""))
        assert cvr_val == pytest.approx(6.67, rel=0.01)

    def test_empty_source_data(self):
        """빈 소스 데이터 엣지 케이스."""
        result = analyze_ai_sources([])
        assert result == {}

    def test_source_without_conversion_stage(self):
        """전환 단계 없는 소스는 conversions=0 — 샘플 데이터."""
        data = [("AI 검색 도착", 100, "Claude")]
        result = analyze_ai_sources(data)
        assert result["Claude"]["conversions"] == 0
        assert result["Claude"]["cvr"] == "0.0%"


# ---------------------------------------------------------------------------
# 5. 인사이트 자동 생성 테스트
# ---------------------------------------------------------------------------


class TestGenerateInsights:
    def setup_method(self):
        """샘플 데이터로 퍼널 및 소스 분석 준비."""
        self.funnel = calculate_funnel(SAMPLE_STAGE_DATA)
        self.sources = analyze_ai_sources(SAMPLE_SOURCE_DATA)

    def test_insights_is_list(self):
        """인사이트 결과가 리스트 — 샘플 데이터."""
        insights = generate_insights(self.funnel, self.sources)
        assert isinstance(insights, list)

    def test_insights_has_max_dropoff(self):
        """최대 이탈 지점 인사이트 포함 — 샘플 데이터."""
        insights = generate_insights(self.funnel, self.sources)
        assert any("최대 이탈" in i for i in insights)

    def test_insights_has_best_source(self):
        """최고 전환 소스 인사이트 포함 — 샘플 데이터."""
        insights = generate_insights(self.funnel, self.sources)
        assert any("최고 전환 소스" in i for i in insights)

    def test_insights_has_worst_source(self):
        """최저 전환 소스 인사이트 포함 — 샘플 데이터."""
        insights = generate_insights(self.funnel, self.sources)
        assert any("최저 전환 소스" in i for i in insights)

    def test_insights_empty_funnel(self):
        """빈 퍼널 엣지 케이스 — 크래시 없어야 함."""
        insights = generate_insights([], {})
        assert isinstance(insights, list)

    def test_insights_min_count(self):
        """인사이트 최소 1개 이상 — 샘플 데이터."""
        insights = generate_insights(self.funnel, self.sources)
        assert len(insights) >= 1


# ---------------------------------------------------------------------------
# 6. 최종 리포트 빌드 테스트
# ---------------------------------------------------------------------------


class TestBuildReport:
    def test_report_has_funnel_key(self):
        """리포트에 funnel 키 존재 — 샘플 데이터."""
        report = build_report(SAMPLE_STAGE_DATA, SAMPLE_SOURCE_DATA)
        assert "funnel" in report

    def test_report_has_ai_source_breakdown_key(self):
        """리포트에 ai_source_breakdown 키 존재 — 샘플 데이터."""
        report = build_report(SAMPLE_STAGE_DATA, SAMPLE_SOURCE_DATA)
        assert "ai_source_breakdown" in report

    def test_report_has_insights_key(self):
        """리포트에 insights 키 존재 — 샘플 데이터."""
        report = build_report(SAMPLE_STAGE_DATA, SAMPLE_SOURCE_DATA)
        assert "insights" in report

    def test_report_json_serializable(self):
        """리포트가 JSON 직렬화 가능 — 샘플 데이터."""
        report = build_report(SAMPLE_STAGE_DATA, SAMPLE_SOURCE_DATA)
        dumped = json.dumps(report, ensure_ascii=False)
        loaded = json.loads(dumped)
        assert loaded["funnel"][0]["stage"] == "AI 검색 도착"

    def test_report_funnel_length(self):
        """퍼널 7단계 — 샘플 데이터."""
        report = build_report(SAMPLE_STAGE_DATA, SAMPLE_SOURCE_DATA)
        assert len(report["funnel"]) == 7

    def test_report_no_source_data(self):
        """소스 데이터 없을 때 리포트 생성 — 샘플 데이터."""
        report = build_report(SAMPLE_STAGE_DATA, [])
        assert report["ai_source_breakdown"] == {}

    def test_report_insurance_domain_sample(self):
        """보험 도메인 샘플 데이터 — 실제 비율 검증."""
        # 보험 상담 신청 퍼널 (샘플 데이터)
        insurance_stages = [
            ("AI 검색 도착", 1000),
            ("콘텐츠 소비", 650),
            ("인터랙션", 200),
            ("관심", 80),
            ("전환", 30),   # 상담 신청
            ("재방문", 15),
            ("추천", 4),
        ]
        report = build_report(insurance_stages, [])
        funnel = report["funnel"]
        # 전환율 3% 확인
        conversion_stage = next(f for f in funnel if f["stage"] == "전환")
        assert float(conversion_stage["rate"].replace("%", "")) == pytest.approx(3.0)
        # 첫 단계→콘텐츠 소비 드롭오프 35%
        content_stage = next(f for f in funnel if f["stage"] == "콘텐츠 소비")
        assert float(content_stage["dropoff"].replace("%", "")) == pytest.approx(35.0)
