"""
tests for weekly-report.py

[task-794.1] P6-2 주간 메트릭 리포트 스크립트 검증
"""

import importlib.util
import json
import sys
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List

import pytest

# 대시 포함 파일명 import
_spec = importlib.util.spec_from_file_location(
    "weekly_report",
    Path(__file__).parent.parent / "weekly-report.py",
)
assert _spec is not None and _spec.loader is not None
wr: Any = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(wr)  # type: ignore[union-attr]


class TestParseDt:
    """parse_dt 함수 테스트"""

    def test_parses_iso_naive(self) -> None:
        """timezone-naive ISO 문자열 파싱"""
        dt = wr.parse_dt("2026-03-22T10:50:37.320125")
        assert dt is not None
        assert dt.year == 2026
        assert dt.month == 3
        assert dt.day == 22
        assert dt.tzinfo is None  # timezone-naive 확인

    def test_strips_z_suffix(self) -> None:
        """'Z' suffix 제거 후 파싱"""
        dt = wr.parse_dt("2026-03-22T10:50:37Z")
        assert dt is not None
        assert dt.tzinfo is None

    def test_returns_none_for_none(self) -> None:
        """None 입력 시 None 반환"""
        assert wr.parse_dt(None) is None

    def test_returns_none_for_empty_string(self) -> None:
        """빈 문자열 입력 시 None 반환"""
        assert wr.parse_dt("") is None

    def test_returns_none_for_invalid_format(self) -> None:
        """잘못된 형식 입력 시 None 반환"""
        assert wr.parse_dt("not-a-date") is None


class TestFilterTasksByPeriod:
    """filter_tasks_by_period 함수 테스트"""

    def _make_task(self, task_id: str, start_offset_days: int, status: str = "completed") -> Dict[str, Any]:
        start_time = (datetime.now() - timedelta(days=start_offset_days)).isoformat()
        return {
            "task_id": task_id,
            "team_id": "dev1-team",
            "start_time": start_time,
            "end_time": None,
            "status": status,
        }

    def test_includes_task_within_period(self) -> None:
        """기간 내 시작된 작업은 포함"""
        tasks = [self._make_task("task-1.1", start_offset_days=3)]
        now = datetime.now()
        result = wr.filter_tasks_by_period(tasks, now - timedelta(days=7), now)
        assert len(result) == 1

    def test_excludes_task_outside_period(self) -> None:
        """기간 밖 작업은 제외"""
        tasks = [self._make_task("task-1.1", start_offset_days=10)]
        now = datetime.now()
        result = wr.filter_tasks_by_period(tasks, now - timedelta(days=7), now)
        assert len(result) == 0

    def test_running_task_included_if_started_in_period(self) -> None:
        """기간 내 시작된 running 작업도 포함"""
        tasks = [self._make_task("task-1.1", start_offset_days=1, status="running")]
        now = datetime.now()
        result = wr.filter_tasks_by_period(tasks, now - timedelta(days=7), now)
        assert len(result) == 1


class TestAggregate:
    """aggregate 함수 테스트"""

    def _make_tasks(self) -> List[Dict[str, Any]]:
        return [
            {
                "task_id": "task-1.1",
                "team_id": "dev1-team",
                "status": "completed",
                "duration_seconds": 300.0,
                "qc_result": "PASS",
            },
            {
                "task_id": "task-1.2",
                "team_id": "dev1-team",
                "status": "completed",
                "duration_seconds": 600.0,
                "qc_result": "FAIL",
            },
            {
                "task_id": "task-1.3",
                "team_id": "dev2-team",
                "status": "running",
                "duration_seconds": None,
                "qc_result": None,
            },
            {
                "task_id": "task-1.4",
                "team_id": "dev2-team",
                "status": "stale",
                "stale_at": "2026-03-21T10:00:00",
                "duration_seconds": 900.0,
            },
        ]

    def test_total_count(self) -> None:
        """총 작업 수 계산"""
        result = wr.aggregate(self._make_tasks())
        assert result["total"] == 4

    def test_completed_count(self) -> None:
        """완료 작업 수 계산"""
        result = wr.aggregate(self._make_tasks())
        assert result["completed"] == 2

    def test_stale_count(self) -> None:
        """stale 작업 수 계산"""
        result = wr.aggregate(self._make_tasks())
        assert result["stale"] == 1

    def test_running_count(self) -> None:
        """running 작업 수 계산"""
        result = wr.aggregate(self._make_tasks())
        assert result["running"] == 1

    def test_qc_counts(self) -> None:
        """QC 집계: PASS 1, FAIL 1, N/A 2"""
        result = wr.aggregate(self._make_tasks())
        assert result["qc_pass"] == 1
        assert result["qc_fail"] == 1
        assert result["qc_na"] == 2

    def test_qc_fail_percentage(self) -> None:
        """QC FAIL 비율: 1/(1+1) = 50%"""
        result = wr.aggregate(self._make_tasks())
        assert result["qc_fail_pct"] == 50.0

    def test_team_avg_minutes(self) -> None:
        """팀별 평균 소요시간 (분 단위)"""
        result = wr.aggregate(self._make_tasks())
        # dev1-team: (300+600)/2 = 450초 = 7.5분
        assert result["team_avg_minutes"]["dev1-team"] == 7.5

    def test_no_qc_data_returns_none_pct(self) -> None:
        """QC 데이터 없을 때 fail_pct=None"""
        tasks = [{"task_id": "task-1.1", "team_id": "dev1-team", "status": "completed", "duration_seconds": 100.0}]
        result = wr.aggregate(tasks)
        assert result["qc_fail_pct"] is None


class TestBuildReport:
    """build_report 함수 테스트"""

    def _make_metrics(self) -> Dict[str, Any]:
        return {
            "total": 10,
            "completed": 9,
            "stale": 0,
            "running": 1,
            "qc_pass": 5,
            "qc_fail": 1,
            "qc_warn": 0,
            "qc_na": 4,
            "qc_fail_pct": 16.7,
            "team_stats": {
                "dev1-team": {
                    "total": 10,
                    "completed": 9,
                    "stale": 0,
                    "running": 1,
                    "duration_seconds_list": [300.0],
                    "qc_pass": 5,
                    "qc_fail": 1,
                    "qc_warn": 0,
                    "qc_na": 4,
                }
            },
            "team_avg_minutes": {"dev1-team": 5.0},
            "stale_details": [],
        }

    def test_contains_header(self) -> None:
        """리포트에 헤더 포함"""
        now = datetime.now()
        report = wr.build_report(self._make_metrics(), [], now, now - timedelta(days=7), now)
        assert "# 주간 메트릭 리포트" in report

    def test_contains_summary(self) -> None:
        """요약 섹션 포함"""
        now = datetime.now()
        report = wr.build_report(self._make_metrics(), [], now, now - timedelta(days=7), now)
        assert "총 작업: 10건" in report
        assert "완료: 9건" in report

    def test_contains_team_stats_table(self) -> None:
        """팀별 통계 표 포함"""
        now = datetime.now()
        report = wr.build_report(self._make_metrics(), [], now, now - timedelta(days=7), now)
        assert "## 팀별 통계" in report
        assert "dev1-team" in report

    def test_stale_section_shows_none_when_empty(self) -> None:
        """stale 없을 때 '해당 없음' 표시"""
        now = datetime.now()
        report = wr.build_report(self._make_metrics(), [], now, now - timedelta(days=7), now)
        assert "(해당 없음)" in report


class TestWeekPeriodCalculation:
    """main()의 전주 월~일 기간 계산 검증"""

    def test_monday_execution_returns_prev_week(self) -> None:
        """월요일 실행 시 전주 월~일 기간"""
        # 2026-04-20은 월요일
        from unittest.mock import patch
        with patch.object(wr, 'now_local', return_value=datetime(2026, 4, 20, 8, 0, 0)):
            # args.days == 7 (기본값) 일 때의 기간 계산 로직 직접 테스트
            now = datetime(2026, 4, 20, 8, 0, 0)
            today = now.date()
            this_monday = today - timedelta(days=today.weekday())
            prev_monday = this_monday - timedelta(days=7)
            prev_sunday = this_monday - timedelta(days=1)
            since = datetime.combine(prev_monday, datetime.min.time())
            until = datetime.combine(prev_sunday, datetime.max.time())

            from datetime import date
            assert prev_monday == date(2026, 4, 13)
            assert prev_sunday == date(2026, 4, 19)
            assert since == datetime(2026, 4, 13, 0, 0, 0)
            assert until.date() == date(2026, 4, 19)
            assert until.hour == 23
            assert until.minute == 59

    def test_wednesday_execution_returns_same_prev_week(self) -> None:
        """수요일 실행해도 동일한 전주 월~일 기간"""
        now = datetime(2026, 4, 22, 14, 30, 0)  # 수요일
        today = now.date()
        this_monday = today - timedelta(days=today.weekday())
        prev_monday = this_monday - timedelta(days=7)
        prev_sunday = this_monday - timedelta(days=1)

        from datetime import date
        assert prev_monday == date(2026, 4, 13)
        assert prev_sunday == date(2026, 4, 19)

    def test_days_option_overrides_week_calculation(self) -> None:
        """--days 옵션 지정 시 기존 방식(now - N일) 사용"""
        now = datetime(2026, 4, 20, 8, 0, 0)
        days = 14  # 기본값(7)이 아닌 값
        until = now
        since = now - timedelta(days=days)
        assert since == datetime(2026, 4, 6, 8, 0, 0)
        assert until == now
