import pandas as pd
import numpy as np
from datetime import datetime
from pandas.api.types import is_datetime64_any_dtype


def split_date_range(claims_df, udate):
    """
    claims_df를 받아서 sdate와 edate를 확인해서 udate 날짜에 걸리는 케이스가 있으면
    그 데이터를 두 개로 쪼갠다. udate에 해당하는 날은 두 번째 데이터에 포함된다.
    
    성능 최적화 버전:
    - 불필요한 복사 최소화 (원본 DataFrame 전체 복사 없이 필요한 부분만 처리)
    - 날짜 변환 최소화 (한 번만 변환)
    - 벡터화 연산 활용 (numpy/pandas 벡터 연산)
    - Early return (분할할 케이스가 없으면 즉시 반환)
    - split_df 복사 최소화 (필요한 부분만 수정)
    
    Parameters:
    -----------
    claims_df : pd.DataFrame
        청구 데이터프레임. 'sdate'와 'edate' 컬럼이 있어야 함 (yyyy-mm-dd 형식)
    udate : str or datetime
        분할 기준 날짜 (yyyy-mm-dd 형식의 문자열 또는 datetime 객체)
    
    Returns:
    --------
    pd.DataFrame
        날짜 범위가 분할된 새로운 데이터프레임
    
    Example:
    --------
    sdate=2017-12-31, edate=2018-01-01이고 udate=2018-01-01이면
    -> sdate=2017-12-31, edate=2017-12-31 / sdate=2018-01-01, edate=2018-01-01
    """
    # udate를 datetime 객체로 변환 (한 번만)
    if isinstance(udate, str):
        udate_dt = pd.to_datetime(udate)
        udate_str = udate
    elif isinstance(udate, datetime):
        udate_dt = pd.to_datetime(udate)
        udate_str = udate.strftime("%Y-%m-%d")
    else:
        raise ValueError("udate는 문자열(yyyy-mm-dd) 또는 datetime 객체여야 합니다.")
    
    # sdate와 edate를 datetime으로 변환 (기존 dtype 유지 시 불필요한 변환 방지)
    sdate_series = claims_df['sdate']
    if is_datetime64_any_dtype(sdate_series):
        sdate_dt = sdate_series.to_numpy(copy=False)
    else:
        sdate_dt = pd.to_datetime(sdate_series).to_numpy(copy=False)

    edate_series = claims_df['edate']
    if is_datetime64_any_dtype(edate_series):
        edate_dt = edate_series.to_numpy(copy=False)
    else:
        edate_dt = pd.to_datetime(edate_series).to_numpy(copy=False)
    
    # 분할 조건: sdate < udate <= edate (벡터화 연산)
    # udate_dt는 pd.Timestamp이므로 numpy 배열과 직접 비교 가능 (브로드캐스팅)
    mask = (sdate_dt < udate_dt) & (edate_dt >= udate_dt)
    
    # Early return: 분할할 케이스가 없으면 원본 반환 (최소 복사)
    if not mask.any():
        return claims_df.copy()
    
    # 분할되지 않은 행들 (복사 최소화)
    no_split_df = claims_df[~mask].copy()
    
    # 분할될 행들 (한 번만 복사하여 두 부분 생성)
    split_df = claims_df[mask].copy()
    
    # udate_minus_one 계산 (한 번만)
    udate_minus_one_dt = udate_dt - pd.Timedelta(days=1)
    
    # 원본 컬럼의 타입 확인 (datetime인지 문자열인지)
    sdate_is_datetime = is_datetime64_any_dtype(claims_df['sdate'])
    edate_is_datetime = is_datetime64_any_dtype(claims_df['edate'])
    
    # 첫 번째 부분: sdate ~ udate-1일 (sdate는 원본 유지, edate는 udate-1일로 변경)
    first_part = split_df.copy()
    if edate_is_datetime:
        # 모든 행에 동일한 datetime 값 할당
        first_part.loc[:, 'edate'] = pd.to_datetime(udate_minus_one_dt)
    else:
        # 모든 행에 동일한 문자열 값 할당
        first_part.loc[:, 'edate'] = udate_minus_one_dt.strftime("%Y-%m-%d")
    
    # 두 번째 부분: udate ~ edate (sdate는 udate로 변경, edate는 원본 유지)
    second_part = split_df.copy()
    if sdate_is_datetime:
        # 모든 행에 동일한 datetime 값 할당
        second_part.loc[:, 'sdate'] = pd.to_datetime(udate_dt)
    else:
        # 모든 행에 동일한 문자열 값 할당
        second_part.loc[:, 'sdate'] = udate_str
    
    # concat (한 번만 실행, ignore_index=True로 인덱스 재정렬 최소화)
    result_df = pd.concat([no_split_df, first_part, second_part], ignore_index=True, copy=False)
    
    return result_df
