"""
데이터 품질 점검 모듈

기본 데이터셋의 품질을 자동으로 점검합니다.
"""

import pandas as pd
import numpy as np
from datetime import datetime
from typing import Tuple


def check_data_quality(ins_df: pd.DataFrame, claims_df: pd.DataFrame) -> Tuple[pd.DataFrame, str]:
    """
    기본 데이터셋의 품질점검을 자동화합니다.
    
    Parameters
    ----------
    ins_df : pd.DataFrame
        가입자 데이터프레임. 컬럼: ID, gender, birth, age, age_band 등
    claims_df : pd.DataFrame
        청구 데이터프레임. 컬럼: ID, gender, birth, kcd, sdate, edate, level 등
    
    Returns
    -------
    Tuple[pd.DataFrame, str]
        - report_df: 품질 점검 결과 DataFrame
        - summary: 요약 문자열
    
    점검 항목:
    1. ID 일치율 (ins_df에 있고 claims_df에 없는 ID 비율)
    2. sdate > edate 오류 탐지
    3. 생년월일-청구일 불일치 (미래 청구 등)
    4. age, gender, level 등 주요 변수 결측·이상치 비율 리포트
    """
    report_items = []
    
    # 날짜 컬럼을 datetime으로 변환
    if 'birth' in ins_df.columns:
        ins_df = ins_df.copy()
        ins_df['birth'] = pd.to_datetime(ins_df['birth'], errors='coerce')
    
    if 'birth' in claims_df.columns:
        claims_df = claims_df.copy()
        claims_df['birth'] = pd.to_datetime(claims_df['birth'], errors='coerce')
    
    if 'sdate' in claims_df.columns:
        claims_df['sdate'] = pd.to_datetime(claims_df['sdate'], errors='coerce')
    
    if 'edate' in claims_df.columns:
        claims_df['edate'] = pd.to_datetime(claims_df['edate'], errors='coerce')
    
    # 1. ID 일치율 점검
    ins_ids = set(ins_df['ID'].unique())
    claims_ids = set(claims_df['ID'].unique())
    
    # claims_df에만 있는 ID (가입 정보 없는데 청구 있음 - 오류)
    only_in_claims = claims_ids - ins_ids
    if len(only_in_claims) > 0:
        only_in_claims_pct = len(only_in_claims) / len(claims_ids) * 100 if len(claims_ids) > 0 else 0
        report_items.append({
            '항목': 'ID 오류 - claims_df에만 있는 ID',
            '값': f"{len(only_in_claims):,}명 ({only_in_claims_pct:.2f}%)",
            '상태': '오류',
            '설명': f'가입 정보(ins_df)가 없는데 청구(claims_df)가 있는 ID - 전체 {len(claims_ids):,}명 중'
        })
    
    # ins_df에만 있는 ID는 정상 (가입은 했지만 청구를 안 한 경우) - 리포트에 포함하지 않음
    
    # 2. sdate > edate 오류 탐지
    if 'sdate' in claims_df.columns and 'edate' in claims_df.columns:
        date_error_mask = claims_df['sdate'] > claims_df['edate']
        date_error_count = date_error_mask.sum()
        date_error_pct = date_error_count / len(claims_df) * 100 if len(claims_df) > 0 else 0
        
        # 결측치 제외한 실제 오류
        valid_mask = claims_df['sdate'].notna() & claims_df['edate'].notna()
        if valid_mask.sum() > 0:
            actual_error_pct = date_error_count / valid_mask.sum() * 100
        else:
            actual_error_pct = 0
        
        report_items.append({
            '항목': '날짜 오류 - sdate > edate',
            '값': f"{date_error_count:,}건 ({actual_error_pct:.2f}%)",
            '상태': '오류' if date_error_count > 0 else '정상',
            '설명': f'시작일이 종료일보다 늦은 레코드 (전체 {len(claims_df):,}건 중)'
        })
    
    # 3. 생년월일-청구일 불일치 (미래 청구 등)
    if 'birth' in claims_df.columns and 'sdate' in claims_df.columns:
        # 생년월일이 청구일보다 나중인 경우
        future_birth_mask = claims_df['birth'] > claims_df['sdate']
        future_birth_count = future_birth_mask.sum()
        
        # 유효한 날짜만 체크
        valid_birth_sdate = claims_df['birth'].notna() & claims_df['sdate'].notna()
        if valid_birth_sdate.sum() > 0:
            future_birth_pct = future_birth_count / valid_birth_sdate.sum() * 100
        else:
            future_birth_pct = 0
        
        report_items.append({
            '항목': '생년월일 오류 - 생년월일 > 청구시작일',
            '값': f"{future_birth_count:,}건 ({future_birth_pct:.2f}%)",
            '상태': '오류' if future_birth_count > 0 else '정상',
            '설명': '생년월일이 청구 시작일보다 나중인 레코드'
        })
    
    # 미래 청구일 체크
    today = pd.Timestamp.now()
    if 'sdate' in claims_df.columns:
        future_claims_mask = claims_df['sdate'] > today
        future_claims_count = future_claims_mask.sum()
        
        valid_sdate = claims_df['sdate'].notna()
        if valid_sdate.sum() > 0:
            future_claims_pct = future_claims_count / valid_sdate.sum() * 100
        else:
            future_claims_pct = 0
        
        report_items.append({
            '항목': '미래 청구 - sdate > 오늘',
            '값': f"{future_claims_count:,}건 ({future_claims_pct:.2f}%)",
            '상태': '경고' if future_claims_count > 0 else '정상',
            '설명': f'청구 시작일이 오늘({today.strftime("%Y-%m-%d")})보다 나중인 레코드'
        })
    
    # 4. 주요 변수 결측치 점검 - ins_df
    ins_missing = {}
    ins_key_cols = ['ID', 'gender', 'birth', 'age']
    for col in ins_key_cols:
        if col in ins_df.columns:
            missing_count = ins_df[col].isna().sum()
            missing_pct = missing_count / len(ins_df) * 100 if len(ins_df) > 0 else 0
            if missing_count > 0:
                ins_missing[col] = (missing_count, missing_pct)
    
    for col, (count, pct) in ins_missing.items():
        report_items.append({
            '항목': f'ins_df 결측치 - {col}',
            '값': f"{count:,}건 ({pct:.2f}%)",
            '상태': '오류' if pct > 1 else '경고' if pct > 0 else '정상',
            '설명': f'ins_df의 {col} 컬럼 결측치'
        })
    
    # 4. 주요 변수 결측치 점검 - claims_df
    claims_missing = {}
    claims_key_cols = ['ID', 'gender', 'birth', 'sdate', 'edate', 'kcd', 'level', 'age']
    for col in claims_key_cols:
        if col in claims_df.columns:
            missing_count = claims_df[col].isna().sum()
            missing_pct = missing_count / len(claims_df) * 100 if len(claims_df) > 0 else 0
            if missing_count > 0:
                claims_missing[col] = (missing_count, missing_pct)
    
    for col, (count, pct) in claims_missing.items():
        report_items.append({
            '항목': f'claims_df 결측치 - {col}',
            '값': f"{count:,}건 ({pct:.2f}%)",
            '상태': '오류' if pct > 5 else '경고' if pct > 0 else '정상',
            '설명': f'claims_df의 {col} 컬럼 결측치'
        })
    
    # 5. 이상치 점검 - age
    if 'age' in ins_df.columns:
        age_negative = (ins_df['age'] < 0).sum()
        age_too_high = (ins_df['age'] > 120).sum()
        if age_negative > 0:
            report_items.append({
                '항목': 'ins_df 이상치 - age < 0',
                '값': f"{age_negative:,}건",
                '상태': '오류',
                '설명': '나이가 음수인 레코드'
            })
        if age_too_high > 0:
            report_items.append({
                '항목': 'ins_df 이상치 - age > 120',
                '값': f"{age_too_high:,}건",
                '상태': '경고',
                '설명': '나이가 120세 초과인 레코드'
            })
    
    if 'age' in claims_df.columns:
        age_negative = (claims_df['age'] < 0).sum()
        age_too_high = (claims_df['age'] > 120).sum()
        if age_negative > 0:
            report_items.append({
                '항목': 'claims_df 이상치 - age < 0',
                '값': f"{age_negative:,}건",
                '상태': '오류',
                '설명': '나이가 음수인 레코드'
            })
        if age_too_high > 0:
            report_items.append({
                '항목': 'claims_df 이상치 - age > 120',
                '값': f"{age_too_high:,}건",
                '상태': '경고',
                '설명': '나이가 120세 초과인 레코드'
            })
    
    # 6. 이상치 점검 - gender
    if 'gender' in ins_df.columns:
        invalid_gender = ~ins_df['gender'].isin([1, 2])
        invalid_gender_count = invalid_gender.sum()
        if invalid_gender_count > 0:
            invalid_gender_pct = invalid_gender_count / len(ins_df) * 100
            report_items.append({
                '항목': 'ins_df 이상치 - gender (1,2 외 값)',
                '값': f"{invalid_gender_count:,}건 ({invalid_gender_pct:.2f}%)",
                '상태': '오류',
                '설명': '성별이 1(남) 또는 2(여)가 아닌 레코드'
            })
    
    if 'gender' in claims_df.columns:
        invalid_gender = ~claims_df['gender'].isin([1, 2])
        invalid_gender_count = invalid_gender.sum()
        if invalid_gender_count > 0:
            invalid_gender_pct = invalid_gender_count / len(claims_df) * 100
            report_items.append({
                '항목': 'claims_df 이상치 - gender (1,2 외 값)',
                '값': f"{invalid_gender_count:,}건 ({invalid_gender_pct:.2f}%)",
                '상태': '오류',
                '설명': '성별이 1(남) 또는 2(여)가 아닌 레코드'
            })
    
    # 7. 이상치 점검 - hosout
    if 'hosout' in claims_df.columns:
        invalid_hosout = ~claims_df['hosout'].isin([1.0, 2.0])
        invalid_hosout_count = invalid_hosout.sum()
        if invalid_hosout_count > 0:
            invalid_hosout_pct = invalid_hosout_count / len(claims_df) * 100
            report_items.append({
                '항목': 'claims_df 이상치 - hosout (1.0,2.0 외 값)',
                '값': f"{invalid_hosout_count:,}건 ({invalid_hosout_pct:.2f}%)",
                '상태': '오류',
                '설명': '입원/통원 여부가 1.0(입원) 또는 2.0(통원)이 아닌 레코드'
            })
    
    # 리포트 DataFrame 생성
    report_df = pd.DataFrame(report_items)
    
    # 요약 문자열 생성
    total_checks = len(report_df)
    error_count = (report_df['상태'] == '오류').sum()
    warning_count = (report_df['상태'] == '경고').sum()
    normal_count = (report_df['상태'] == '정상').sum()
    
    summary_lines = [
        "=" * 80,
        "데이터 품질 점검 리포트",
        "=" * 80,
        f"전체 점검 항목: {total_checks}개",
        f"  - 정상: {normal_count}개",
        f"  - 경고: {warning_count}개",
        f"  - 오류: {error_count}개",
        "",
        f"ins_df: {len(ins_df):,}건, {len(ins_ids):,}명의 고객",
        f"claims_df: {len(claims_df):,}건, {len(claims_ids):,}명의 고객",
        "",
    ]
    
    if error_count > 0:
        summary_lines.extend([
            "오류 항목:",
            report_df[report_df['상태'] == '오류'][['항목', '값']].to_string(index=False),
            ""
        ])
    
    if warning_count > 0:
        summary_lines.extend([
            "경고 항목:",
            report_df[report_df['상태'] == '경고'][['항목', '값']].to_string(index=False),
            ""
        ])
    
    summary_lines.append("=" * 80)
    summary = "\n".join(summary_lines)
    
    return report_df, summary

