import os
import shutil
import subprocess
from datetime import datetime, timedelta

import pandas as pd

# --- 1. 실습 환경 설정 ---
# Feast Feature Repository를 저장할 디렉토리를 생성합니다.
repo_path = "feature_repo"
if os.path.exists(repo_path):
    shutil.rmtree(repo_path)
os.makedirs(os.path.join(repo_path, "data"), exist_ok=True)

print(f"'{repo_path}' 디렉토리를 생성했습니다.")

# --- 2. 샘플 데이터 생성 ---
# 오프라인 스토어에 저장될 샘플 데이터를 생성합니다.
# 이 데이터는 운전자별 시간당 주행 통계를 나타냅니다.
end_date = datetime.now()
start_date = end_date - timedelta(days=7)
n_drivers = 10

driver_ids = [1000 + i for i in range(n_drivers)]
data = {
    "event_timestamp": [],
    "driver_id": [],
    "conv_rate": [],
    "acc_rate": [],
    "avg_daily_trips": [],
}

for driver_id in driver_ids:
    current_date = start_date
    while current_date < end_date:
        data["event_timestamp"].append(current_date)
        data["driver_id"].append(driver_id)
        data["conv_rate"].append(pd.np.random.uniform(0, 1))
        data["acc_rate"].append(pd.np.random.uniform(0, 1))
        data["avg_daily_trips"].append(pd.np.random.randint(0, 100))
        current_date += timedelta(hours=1)

df = pd.DataFrame(data)
driver_stats_path = os.path.join(repo_path, "data", "driver_stats.parquet")
df.to_parquet(driver_stats_path)

print(f"샘플 데이터 '{driver_stats_path}'를 생성했습니다.")

# --- 3. Feature Repository 정의 ---
# Feast의 핵심 구성 요소(Entity, Feature View, Data Source 등)를 정의하는
# feature_store.yaml 파일과 피처 정의 python 파일을 생성합니다.

feature_store_yaml = f"""
project: my_driver_project
registry: {os.path.join(repo_path, 'data', 'registry.db')}
provider: local
online_store:
    type: sqlite
    path: {os.path.join(repo_path, 'data', 'online.db')}
"""

with open(os.path.join(repo_path, "feature_store.yaml"), "w") as f:
    f.write(feature_store_yaml)

print(f"'{os.path.join(repo_path, 'feature_store.yaml')}' 파일을 생성했습니다.")

driver_repo_py = f"""
from datetime import timedelta
from feast import Entity, FeatureView, Field, FileSource, ValueType
from feast.types import Float32, Int64

# 운전자 ID를 Entity로 정의합니다.
driver = Entity(name="driver_id", value_type=ValueType.INT64, description="driver id")

# 오프라인 데이터 소스를 정의합니다.
driver_stats_source = FileSource(
    path="{driver_stats_path}",
    event_timestamp_column="event_timestamp",
)

# Feature View를 정의하여 피처들을 그룹화하고 데이터 소스에 연결합니다.
driver_stats_fv = FeatureView(
    name="driver_hourly_stats",
    entities=[driver],
    ttl=timedelta(days=1),
    schema=[
        Field(name="conv_rate", dtype=Float32),
        Field(name="acc_rate", dtype=Float32),
        Field(name="avg_daily_trips", dtype=Int64),
    ],
    online=True,
    source=driver_stats_source,
    tags={{"team": "driver_performance"}},
)
"""

with open(os.path.join(repo_path, "driver_repo.py"), "w") as f:
    f.write(driver_repo_py)

print(f"'{os.path.join(repo_path, 'driver_repo.py')}' 파일을 생성했습니다.")

# --- 4. 피처 등록 및 구체화 ---
# 'feast apply' 명령어를 실행하여 Feature Repository에 피처 정의를 등록(등록)합니다.
# 'feast materialize' 명령어로 오프라인 스토어의 데이터를 온라인 스토어로 로딩합니다.

print("\n--- Feast Apply 실행 ---")
subprocess.run(["feast", "apply"], cwd=repo_path, check=True)

print("\n--- Feast Materialize 실행 ---")
materialize_end_date = datetime.now()
materialize_start_date = materialize_end_date - timedelta(days=7)
subprocess.run(
    [
        "feast",
        "materialize",
        materialize_end_date.strftime("%Y-%m-%d"),
        materialize_start_date.strftime("%Y-%m-%d"),
    ],
    cwd=repo_path,
    check=True,
)


# --- 5. 학습 데이터셋 생성 및 모델 학습 ---
# get_historical_features()를 사용하여 과거 시점의 피처들을 가져와 학습 데이터셋을 만듭니다.

print("\n--- 학습 데이터셋 생성 ---")
from feast import FeatureStore
from sklearn.linear_model import LinearRegression

# FeatureStore 객체를 초기화합니다.
store = FeatureStore(repo_path=repo_path)

# 학습에 사용할 Entity와 타임스탬프를 정의합니다.
entity_df = pd.DataFrame(
    {
        "event_timestamp": [
            end_date - timedelta(days=1),
            end_date - timedelta(hours=12),
            end_date - timedelta(hours=6),
        ]
        * n_drivers,
        "driver_id": sorted(driver_ids * 3),
    }
)

# Feast를 통해 학습 데이터를 가져옵니다.
training_df = store.get_historical_features(
    entity_df=entity_df,
    features=[
        "driver_hourly_stats:conv_rate",
        "driver_hourly_stats:acc_rate",
        "driver_hourly_stats:avg_daily_trips",
    ],
).to_df()

print("생성된 학습 데이터셋:")
print(training_df.head())

# 가상의 타겟 변수(label)를 생성합니다.
training_df["label_driver_good"] = (
    training_df["conv_rate"] + training_df["acc_rate"] / 1000
) > 0.8

# 모델을 학습시킵니다.
features = ["conv_rate", "acc_rate", "avg_daily_trips"]
target = "label_driver_good"

reg = LinearRegression()
reg.fit(training_df[features], training_df[target])
print("\n간단한 선형 회귀 모델을 학습시켰습니다.")

# --- 6. 실시간 추론을 위한 온라인 피처 조회 ---
# get_online_features()를 사용하여 실시간 예측에 필요한 최신 피처를 조회합니다.

print("\n--- 온라인 피처 조회 및 실시간 예측 ---")
feature_vector = store.get_online_features(
    features=[
        "driver_hourly_stats:conv_rate",
        "driver_hourly_stats:acc_rate",
        "driver_hourly_stats:avg_daily_trips",
    ],
    entity_rows=[{"driver_id": 1001}, {"driver_id": 1002}],
).to_dict()

online_features_df = pd.DataFrame.from_dict(feature_vector)
print("온라인 스토어에서 조회한 피처:")
print(online_features_df)

# 온라인 피처를 사용하여 예측을 수행합니다.
predictions = reg.predict(online_features_df[features])
print(f"\n실시간 예측 결과 (1001, 1002 드라이버): {predictions}")

# --- 7. 정리 ---
# 실습을 위해 생성했던 디렉토리를 삭제합니다.
# cleanup = input("생성된 feature_repo 디렉토리를 삭제하시겠습니까? (y/n): ")
# if cleanup.lower() == 'y':
#     shutil.rmtree(repo_path)
#     print(f"'{repo_path}' 디렉토리를 삭제했습니다.")
print(f"\n실습 완료! '{repo_path}'에 생성된 파일들을 확인해보세요.")
print(f"정리를 원하시면 'rm -rf {repo_path}' 명령어를 실행하세요.")
