import argparse
from pathlib import Path
import sys


FILES = {
    "db": (
        "db.py",
        """import psycopg2
import pandas as pd
import csv


# подключение
conn = psycopg2.connect(
    user="postgres",
    password="postgres",
    host="127.0.0.1",
    port=5432,
    dbname="testdb"
)
cur = conn.cursor()

cur.execute('''
DROP TABLE IF EXISTS tracks;
CREATE TABLE tracks(
    "Трек" INTEGER,
    "Дата" TEXT,
    "Время" TEXT,
    "Регион" TEXT,
    "Координаты" TEXT,
    "Частота шагов" REAL,
    "Высота" REAL,
    "Температура" TEXT,
    "Местность" TEXT,
    "Объекты вокруг" TEXT
);
''')
conn.commit()


with open('track_data.csv', 'r', encoding='utf-8') as file:
    reader = csv.reader(file, delimiter=';')
    df = list(reader)
    df.pop(0)

for i in range(len(df)):
    sql = f''' INSERT INTO tracks(
    "Трек",
    "Дата",
    "Время",
    "Регион",
    "Координаты",
    "Частота шагов",
    "Высота",
    "Температура",
    "Местность",
    "Объекты вокруг")
    VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
'''
    cur.execute(sql, df[i])
    conn.commit()
conn.close()



"""
    ),
    "parser": (
        "parser.py",
        """import geopandas as gpd
import pandas as pd
import requests
import overpy
from geopy.geocoders import Nominatim
from datetime import timezone
from collections import defaultdict
from math import radians, sin, cos, sqrt, atan2
import matplotlib.pyplot as plt
import os
import matplotlib.pyplot as plt
import contextily as ctx
from shapely.geometry import LineString

geolocator = Nominatim(user_agent="gpx-analyzer")
overpass = overpy.Overpass(
    url="https://overpass.kumi.systems/api/interpreter"
)
weather_cache = defaultdict(dict)

def fetch_gpx_points(filepath):
    return gpd.read_file(filepath, layer="track_points")

def get_region(point):
    try:
        loc = geolocator.reverse((point.y, point.x), timeout=10)
        return loc.raw.get("address", {}).get("state", "Неизвестно")
    except:
        return "Неизвестно"

def get_location_type(lat, lon):
    try:
        address = geolocator.reverse((lat, lon), timeout=10).raw.get("address", {})
        if "suburb" in address: return "Пригород"
        if "city" in address or "town" in address: return "Город"
        if "village" in address or "hamlet" in address: return "Сельская местность"
        return "Природная зона"
    except:
        return "Неизвестно"

def get_surroundings(lat, lon, radius=500):
    try:
        result = overpass.query(f'''
        (
            node(around:{radius},{lat},{lon});
            way(around:{radius},{lat},{lon});
            relation(around:{radius},{lat},{lon});
        );
        out tags;
        ''')
        tags = {
            tag + ":" + elem.tags[tag]
            for elem in result.nodes + result.ways + result.relations
            for tag in ["natural", "landuse", "amenity", "leisure", "building"]
            if tag in elem.tags
        }
        return ", ".join(sorted(tags)) if tags else "ничего интересного"
    except:
        return "неизвестно"

def estimate_step_frequency(gdf):
    if "time" not in gdf.columns:
        return None
    gdf["time"] = pd.to_datetime(gdf["time"], errors="coerce")
    if gdf["time"].isnull().all():
        return None
    return round(gdf.groupby(gdf["time"].dt.floor("min")).size().mean(), 2)

def round_to_grid(lat, lon, step=0.25):
    return round(lat / step) * step, round(lon / step) * step

def get_temperature(lat, lon, time_point):
    if pd.isnull(time_point):
        return "—"
    
    dt = pd.Timestamp(time_point).tz_localize('UTC') if time_point.tzinfo is None else pd.Timestamp(time_point).tz_convert('UTC')
    dt = dt.replace(minute=0, second=0, microsecond=0)
    hour_key = dt.isoformat()

    # округляем до сетки ERA5
    lat, lon = round_to_grid(lat, lon)
    coord_key = (lat, lon)

    if coord_key not in weather_cache:
        weather_cache[coord_key] = {}

    if hour_key in weather_cache[coord_key]:
        return weather_cache[coord_key][hour_key]

    try:
        params = {
            "latitude": lat,
            "longitude": lon,
            "start_date": dt.date().isoformat(),
            "end_date": dt.date().isoformat(),
            "hourly": "temperature_2m",
            "timezone": "UTC"
        }
        url = "https://archive-api.open-meteo.com/v1/era5"
        r = requests.get(url, params=params)
        data = r.json()

        hourly = data.get("hourly", {})
        times = pd.to_datetime(hourly.get("time", []), utc=True)
        temps = hourly.get("temperature_2m", [])

        if len(times) == 0 or len(temps) == 0:
            weather_cache[coord_key][hour_key] = "неизвестно"
            return "неизвестно"

        idx = (abs(times - dt)).argmin()
        temp = temps[idx]

        weather_cache[coord_key][hour_key] = temp
        return temp

    except Exception:
        weather_cache[coord_key][hour_key] = "неизвестно"
        return "неизвестно"


def haversine(p1, p2):
    R = 6371000
    lat1, lon1, lat2, lon2 = map(radians, [p1.y, p1.x, p2.y, p2.x])
    dlat, dlon = lat2 - lat1, lon2 - lon1
    a = sin(dlat/2)**2 + cos(lat1)*cos(lat2)*sin(dlon/2)**2
    return R * 2 * atan2(sqrt(a), sqrt(1 - a))


def save_track_image(gdf, track_id):
    os.makedirs("images", exist_ok=True)

    pts = list(gdf.geometry)
    if len(pts) < 2:
        return

    line = LineString([(p.x, p.y) for p in pts])
    line_gdf = gdf.iloc[:1].copy()
    line_gdf["geometry"] = [line]
    line_gdf = line_gdf.set_crs(epsg=4326)

    line_3857 = line_gdf.to_crs(epsg=3857)

    fig, ax = plt.subplots(figsize=(7, 7))


    line_3857.plot(ax=ax, color="red", linewidth=3)


    ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik)

    ax.set_axis_off()
    ax.set_title(f"Маршрут {track_id}")
    ax = ctx.add_basemap(ax, zoom=15)

    plt.savefig(f"images/track_{track_id}.png", dpi=150, bbox_inches="tight")
    plt.close()
    
def process_track(filepath, track_num):
    print(f"Трек {track_num}: {filepath}")
    gdf = fetch_gpx_points(filepath)
    if gdf.empty:
        print("Пустой трек")
        return pd.DataFrame()
    
    save_track_image(gdf, track_num)

    gdf["time"] = pd.to_datetime(gdf["time"], errors="coerce")
    gdf = gdf.to_crs(epsg=4326)
    step_freq = estimate_step_frequency(gdf)
    region = get_region(gdf.geometry.iloc[0])

    records = []
    last_env_point = None
    env_type, env_desc = "Неизвестно", "неизвестно"

    for _, row in gdf.iterrows():
        pt = row.geometry
        lat, lon = pt.y, pt.x
        time_point = row.get("time").replace(microsecond=0)

        pd.notnull(time_point)
        date = time_point.date()         
        time = time_point.time()  
        ele = row.get("ele")

        if last_env_point is None or haversine(pt, last_env_point) >= 500:
            env_type = get_location_type(lat, lon)
            env_desc = get_surroundings(lat, lon)
            last_env_point = pt

        temp = get_temperature(lat, lon, time_point)

        records.append({
            "Трек": track_num,
            "Дата": date,
            "Время": time,
            "Регион": region,
            "Координаты": (lat, lon),
            "Частота шагов": step_freq,
            "Высота": ele,
            "Температура": temp,
            "Местность": env_type,
            "Объекты вокруг": env_desc
        })

    return pd.DataFrame(records)



if __name__ == "__main__":
    files = [
    ]
    all_tracks = [process_track(f, i+1) for i, f in enumerate(files)]
    
    result = pd.concat(all_tracks, ignore_index=True)
    result.to_csv("track_data.csv", index=False, sep=';')
    print("Сохранено в 'track_data.csv'")
"""


    ),
    "plot": (
        "plot.py",
        """import matplotlib.pyplot as plt
import contextily as ctx
import gpxpy
import geopandas as gpd
from shapely.geometry import LineString
from pathlib import Path

gpx_dir = Path("gpx")
out_dir = Path("images")

out_dir.mkdir(exist_ok=True)

for gpx_file in gpx_dir.glob("*.gpx"):
    print(f"Обрабатывается {gpx_file.name}")

    with open(gpx_file, "r", encoding="utf-8") as f:
        gpx = gpxpy.parse(f)

    points = []
    for track in gpx.tracks:
        for segment in track.segments:
            for p in segment.points:
                points.append((p.longitude, p.latitude))

    line = LineString(points)

    gdf = gpd.GeoDataFrame(geometry=[line], crs="EPSG:4326")
    gdf = gdf.to_crs(epsg=3857)

    fig, ax = plt.subplots(figsize=(20, 20))

    gdf.plot(ax=ax, linewidth=2)
    ctx.add_basemap(ax, source=ctx.providers.OpenStreetMap.Mapnik)

    ax.set_axis_off()

    out_path = out_dir / f"{gpx_file.stem}.png"
    plt.savefig(out_path, dpi=200, bbox_inches="tight")
    plt.close(fig)


print("Все файлы обработаны")
"""
    ),
    "2": (
        "app.py",
        """import jwt
import time
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def dashboard():
    METABASE_SITE_URL = "http://localhost:3000"
    METABASE_SECRET_KEY = "a771ed707079fb33b6ab31fd45a253cfd50a98ff71fb5511ff1c42ed4b1fb88b"

    payload = {
    "resource": {"dashboard": 3},
    "params": {
        
    },
    "exp": round(time.time()) + (60 * 1) # 10 minute expiration
    }
    token = jwt.encode(payload, METABASE_SECRET_KEY, algorithm="HS256")

    embed_url = f"{METABASE_SITE_URL}/embed/dashboard/{token}#bordered=true&titled=true"

    return render_template('dashboard.html', embed_url=embed_url)

if __name__ == '__main__':
    app.run(debug=True)

"""
    ),
    "21": (
        "dashboard.html",
        """<!DOCTYPE html>
<html>
<body>
    <h1>Мой Dashboard Metabase</h1>
    <iframe
        src="{{ embed_url }}"
        frameborder="0"
        width="100%"
        height="900"
        allowtransparency
    ></iframe>
</body>
</html>
"""),
    "23": (
        "markup.py",
        """import pandas as pd
import numpy as np
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

df = pd.read_csv("track_data.csv", sep=';')

def get_season(date_str):
    try:
        month = pd.to_datetime(date_str).month
        if month in [12, 1, 2]: return "Зима"
        if month in [3, 4, 5]: return "Весна"
        if month in [6, 7, 8]: return "Лето"
        if month in [9, 10, 11]: return "Осень"
    except: return "Неизвестно"

def is_fire_risk(temp, season):
    return season == "Лето" and temp is not None and temp > 25

def is_flood_risk(env, ele):
    return "река" in env.lower() or "болото" in env.lower() or (ele is not None and ele < 100)

def evac_difficulty(env_type):
    if env_type in ["город", "пригород"]: return 0
    if env_type in ["поле", "равнина"]: return 0.5
    return 1  

df["season"] = df["Дата"].apply(get_season)
df["fire_risk"] = df.apply(lambda x: int(is_fire_risk(x["Температура"], x["season"])), axis=1)
df["flood_risk"] = df.apply(lambda x: int(is_flood_risk(x["Объекты вокруг"], x["Высота"])), axis=1)
df["evac_difficulty"] = df["Местность"].apply(evac_difficulty)

df["elevation"] = df["Высота"].fillna(df["Высота"].mean())
df["temperature"] = df["Температура"].apply(lambda x: x if isinstance(x, (int, float)) else np.nan).fillna(df["Температура"].mean())
df["season_num"] = df["season"].map({"Зима": 0, "Весна": 1, "Лето": 2, "Осень": 3}).fillna(0)

fire_features = df[["elevation", "temperature", "season_num", "fire_risk", "evac_difficulty"]]
fire_scaled = StandardScaler().fit_transform(fire_features)
fire_model = KMeans(n_clusters=3, random_state=42).fit(fire_scaled)
df["Кластер_Пожар"] = fire_model.labels_

flood_features = df[["elevation", "season_num", "flood_risk", "evac_difficulty"]]
flood_scaled = StandardScaler().fit_transform(flood_features)
flood_model = KMeans(n_clusters=3, random_state=42).fit(flood_scaled)
df["Кластер_Затопление"] = flood_model.labels_

df.to_csv("track_data_with_clusters.csv", index=False)
print("Результаты сохранены в 'track_data_with_clusters.csv'")

"""),
    "24":(
        "analitic.py",
        """import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.mixture import GaussianMixture
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score, davies_bouldin_score

df = pd.read_csv("track_data.csv", sep=';')

def get_season(date_str):
    try:
        month = pd.to_datetime(date_str).month
        if month in [12, 1, 2]: return "Зима"
        if month in [3, 4, 5]: return "Весна"
        if month in [6, 7, 8]: return "Лето"
        if month in [9, 10, 11]: return "Осень"
    except: return "Неизвестно"

def is_fire_risk_from_env(env_text):
    env_text = env_text.lower()
    return any(keyword in env_text for keyword in ["tree", "wood", "grass"])

def is_flood_risk_from_env(env_text):
    env_text = env_text.lower()
    return any(keyword in env_text for keyword in ["water"])

df["fire_risk"] = df["Объекты вокруг"].fillna("").apply(is_fire_risk_from_env).astype(int)
df["flood_risk"] = df["Объекты вокруг"].fillna("").apply(is_flood_risk_from_env).astype(int)
df["season"] = df["Дата"].apply(get_season)
df["season_num"] = df["season"].map({"Зима": 0, "Весна": 1, "Лето": 2, "Осень": 3}).fillna(0)

fire_features = df[["season_num", "Температура", "fire_risk"]]
fire_scaled = StandardScaler().fit_transform(fire_features)

flood_features = df[["season_num", "Высота", "flood_risk"]]
flood_scaled = StandardScaler().fit_transform(flood_features)

def evaluate_clustering(X, labels, method_name):
    print(f"\n🔍 Метод: {method_name}")
    try:
        sil_score = silhouette_score(X, labels)
        db_score = davies_bouldin_score(X, labels)
        print(f"Silhouette Score: {sil_score:.3f}")
        print(f"Davies-Bouldin Index: {db_score:.3f}")
    except Exception as e:
        print(f"Оценка недоступна: {e}")

    # PCA + визуализация
    plt.figure(figsize=(6, 5))
    sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=labels, palette="Set2", s=60)
    plt.title(f"{method_name}")
    plt.xlabel("PCA1")
    plt.ylabel("PCA2")
    plt.legend(title="Кластер", loc="best")
    plt.tight_layout()
    plt.savefig(f'images/{method_name}', dpi=300, bbox_inches='tight')

print("\n===== Кластеризация: Пожар =====")
# KMeans
fire_model = KMeans(n_clusters=3, random_state=42)
labels_kmeans_fire = fire_model.fit_predict(fire_scaled)

evaluate_clustering(fire_scaled, labels_kmeans_fire, "KMeans (Пожар)")

dbscan_fire = DBSCAN(eps=0.8, min_samples=5)
labels_dbscan_fire = dbscan_fire.fit_predict(fire_scaled)
evaluate_clustering(fire_scaled, labels_dbscan_fire, "DBSCAN (Пожар)")

agg_fire = AgglomerativeClustering(n_clusters=3)
labels_agg_fire = agg_fire.fit_predict(fire_scaled)
df["Кластер_Пожар"] = labels_agg_fire
evaluate_clustering(fire_scaled, labels_agg_fire, "Agglomerative (Пожар)")

gmm_fire = GaussianMixture(n_components=3, random_state=42)
labels_gmm_fire = gmm_fire.fit_predict(fire_scaled)
evaluate_clustering(fire_scaled, labels_gmm_fire, "GMM (Пожар)")

print("\n===== Кластеризация: Затопление =====")
# KMeans
flood_model = KMeans(n_clusters=3, random_state=42)
labels_kmeans_flood = flood_model.fit_predict(flood_scaled)

evaluate_clustering(flood_scaled, labels_kmeans_flood, "KMeans (Затопление)")

dbscan_flood = DBSCAN(eps=0.8, min_samples=5)
labels_dbscan_flood = dbscan_flood.fit_predict(flood_scaled)
evaluate_clustering(flood_scaled, labels_dbscan_flood, "DBSCAN (Затопление)")

agg_flood = AgglomerativeClustering(n_clusters=3)
labels_agg_flood = agg_flood.fit_predict(flood_scaled)
df["Кластер_Затопление"] = labels_agg_flood
evaluate_clustering(flood_scaled, labels_agg_flood, "Agglomerative (Затопление)")

gmm_flood = GaussianMixture(n_components=3, random_state=42)
labels_gmm_flood = gmm_flood.fit_predict(flood_scaled)
evaluate_clustering(flood_scaled, labels_gmm_flood, "GMM (Затопление)")

df.to_csv("track_data_with_clusters_updated.csv", index=False)
print("\n Кластеры обновлены и сохранены в 'track_data_with_clusters_updated.csv'")
"""
 ),
 "3":(
        "train_model.py",
        """import os
import joblib
import pandas as pd

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, recall_score, f1_score, roc_auc_score
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier


DATA_PATH = "track_data_with_clusters_updated.csv"
MODEL_DIR = "saved_models"
TEST_SIZE = 0.2
RANDOM_STATE = 42


df = pd.read_csv(DATA_PATH)

df["risk_level"] = df[["fire_risk", "flood_risk"]].max(axis=1)

FEATURES = [
    "Частота шагов",
    "Высота",
    "Температура",
    "season_num",
    "Кластер_Пожар",
    "Кластер_Затопление"
]

X = df[FEATURES]
y = df["risk_level"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE
)


models = {
    "LogisticRegression": LogisticRegression(max_iter=1000),
    "DecisionTree": DecisionTreeClassifier(random_state=RANDOM_STATE),
    "RandomForest": RandomForestClassifier(random_state=RANDOM_STATE)
}

results = {}


for name, model in models.items():
    model.fit(X_train, y_train)

    y_pred = model.predict(X_test)
    y_proba = model.predict_proba(X_test)[:, 1]

    metrics = {
        "accuracy": accuracy_score(y_test, y_pred),
        "recall": recall_score(y_test, y_pred),
        "f1": f1_score(y_test, y_pred),
        "roc_auc": roc_auc_score(y_test, y_proba)
    }

    results[name] = {
        "model": model,
        "metrics": metrics
    }

    print(f"\n🔹 {name}")
    for k, v in metrics.items():
        print(f"{k}: {v:.3f}")


best_model_name = max(results, key=lambda x: results[x]["metrics"]["f1"])
best_model = results[best_model_name]["model"]

print(f"\n Лучшая модель: {best_model_name}")


os.makedirs(MODEL_DIR, exist_ok=True)
model_path = os.path.join(MODEL_DIR, f"{best_model_name}.pkl")
joblib.dump(best_model, model_path)

print(f"Модель сохранена: {model_path}")

"""
 ),
 "3":(
        "train_model.py",
        """
import os
import joblib
import pandas as pd

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, recall_score, f1_score, roc_auc_score
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier


DATA_PATH = "track_data_with_clusters_updated.csv"
MODEL_DIR = "saved_models"
TEST_SIZE = 0.2
RANDOM_STATE = 42


df = pd.read_csv(DATA_PATH)

df["risk_level"] = df[["fire_risk", "flood_risk"]].max(axis=1)

FEATURES = [
    "Частота шагов",
    "Высота",
    "Температура",
    "season_num",
    "Кластер_Пожар",
    "Кластер_Затопление"
]

X = df[FEATURES]
y = df["risk_level"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE
)


models = {
    "LogisticRegression": LogisticRegression(max_iter=1000),
    "DecisionTree": DecisionTreeClassifier(random_state=RANDOM_STATE),
    "RandomForest": RandomForestClassifier(random_state=RANDOM_STATE)
}

results = {}


for name, model in models.items():
    model.fit(X_train, y_train)

    y_pred = model.predict(X_test)
    y_proba = model.predict_proba(X_test)[:, 1]

    metrics = {
        "accuracy": accuracy_score(y_test, y_pred),
        "recall": recall_score(y_test, y_pred),
        "f1": f1_score(y_test, y_pred),
        "roc_auc": roc_auc_score(y_test, y_proba)
    }

    results[name] = {
        "model": model,
        "metrics": metrics
    }

    print(f"\n🔹 {name}")
    for k, v in metrics.items():
        print(f"{k}: {v:.3f}")


best_model_name = max(results, key=lambda x: results[x]["metrics"]["f1"])
best_model = results[best_model_name]["model"]

print(f"\n Лучшая модель: {best_model_name}")


os.makedirs(MODEL_DIR, exist_ok=True)
model_path = os.path.join(MODEL_DIR, f"{best_model_name}.pkl")
joblib.dump(best_model, model_path)

print(f"Модель сохранена: {model_path}")
 
""")
}


def create_file(name: str, force: bool):
    if name not in FILES:
        print(f"Неизвестный файл: {name}")
        print("Доступно:", ", ".join(FILES.keys()))
        sys.exit(1)

    filename, content = FILES[name]
    path = Path(filename)

    if path.exists() and not force:
        print(f"Файл {filename} уже существует. Используй --force")
        sys.exit(1)

    path.write_text(content, encoding="utf-8")
    print(f"Создан файл: {filename}")


def main():
    parser = argparse.ArgumentParser(prog="sall")
    parser.add_argument(
        "name",
        help="Какой файл создать (db, parser, plot, mestnost)"
    )
    parser.add_argument(
        "--force",
        action="store_true",
        help="Перезаписать файл, если существует"
    )

    args = parser.parse_args()
    create_file(args.name, args.force)
