import os, sys, django, time
os.environ['DJANGO_SETTINGS_MODULE'] = 'tianxuan.settings'
os.environ['DJANGO_ALLOW_ASYNC_UNSAFE'] = 'true'
sys.path.insert(0, r'C:\Users\25044\Desktop\Proj\天璇')
django.setup()

import polars as pl, numpy as np
from analysis.data_loader import load_from_db

lf = load_from_db('_data_43')
if lf is None:
    print('_data_43 not found')
    sys.exit(1)

# Coerce key columns to numeric (simulating background process)
schema = lf.collect_schema()
names = schema.names()
print(f'Columns: {len(names)}')

# Pick numeric candidates
numeric_candidates = []
utf8_cols = []
for n, d in zip(names, list(schema.dtypes())):
    if d == pl.Utf8 and not n.startswith('_'):
        utf8_cols.append(n)

# Sample to check castability
sample = lf.select(utf8_cols).head(500).collect(streaming=True)
type_map = {}
for col in utf8_cols:
    try:
        casted = sample[col].cast(pl.Float64, strict=False)
        nn = casted.is_not_null().sum()
        if nn > len(casted) * 0.5:
            type_map[col] = pl.Float64
    except:
        pass

print(f'Coercible columns: {len(type_map)}: {list(type_map.keys())[:8]}')

# Apply casts
casts = [pl.col(c).cast(pl.Float64, strict=False) for c in type_map]
lf = lf.with_columns(casts)

# Find numeric columns
numeric_types = (pl.Int8, pl.Int16, pl.Int32, pl.Int64, pl.UInt8, pl.UInt16, pl.UInt32, pl.UInt64, pl.Float32, pl.Float64)
schema2 = lf.collect_schema()
feature_cols = [schema2.names()[i] for i, dt in enumerate(list(schema2.dtypes())) 
                if dt in numeric_types and not schema2.names()[i].startswith('_')][:10]
print(f'Feature cols ({len(feature_cols)}): {feature_cols}')

# Collect and run
t0 = time.time()
df = lf.select(feature_cols).collect(streaming=True)
data = df.to_numpy().astype(np.float64)
print(f'Data shape: {data.shape}, nans: {np.isnan(data).sum()}')

col_mean = np.nanmean(data, axis=0)
col_mean = np.nan_to_num(col_mean, nan=0.0)
data = np.where(np.isnan(data), col_mean, data)

if len(data) > 50000:
    rng = np.random.default_rng(42)
    data = data[rng.choice(len(data), 50000, replace=False)]
print(f'After sample: {data.shape}')

from sklearn.preprocessing import StandardScaler
print('Scaling...')
data_scaled = StandardScaler().fit_transform(data)
print(f'Scaled shape: {data_scaled.shape}')

from sklearn.cluster import HDBSCAN
print('HDBSCAN...')
model = HDBSCAN(min_cluster_size=5, min_samples=5)
labels = model.fit_predict(data_scaled)
nc = len(set(labels)) - (1 if -1 in labels else 0)
print(f'DONE in {time.time()-t0:.1f}s: {nc} clusters, {(labels==-1).sum()} noise')
