import numpy as np
import pandas as pd
import requests
from flask import Flask, request, jsonify
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.layers import LSTM, Dense, Dropout
from scipy.optimize import minimize
import joblib
import sqlite3
from datetime import datetime, timedelta
import threading
import os
import logging
from sklearn.preprocessing import MinMaxScaler

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("fuel_optimization.log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

app = Flask(__name__)

# 配置开关 - 设置为True使用完全模拟数据
SIMULATION_MODE = True


# ==================== 数据采集模块 ====================
def setup_database():
    """初始化数据库"""
    try:
        conn = sqlite3.connect('fuel_data.db')
        c = conn.cursor()

        # 创建加油站信息表
        c.execute('''CREATE TABLE IF NOT EXISTS stations
                     (id TEXT PRIMARY KEY, 
                      name TEXT, 
                      lat REAL, 
                      lng REAL, 
                      brand TEXT,
                      city TEXT)''')

        # 创建历史油价表
        c.execute('''CREATE TABLE IF NOT EXISTS prices
                     (station_id TEXT, 
                      date DATE, 
                      price REAL,
                      PRIMARY KEY (station_id, date))''')

        # 创建实时路况缓存表
        c.execute('''CREATE TABLE IF NOT EXISTS traffic
                     (route TEXT PRIMARY KEY, 
                      congestion_index REAL, 
                      timestamp TIMESTAMP)''')

        conn.commit()
        conn.close()
        logger.info("数据库初始化完成")
    except Exception as e:
        logger.error(f"数据库初始化失败: {str(e)}")


def fetch_gas_stations(city):
    """获取加油站数据"""
    if SIMULATION_MODE:
        logger.info(f"使用模拟数据获取{city}的加油站")
        return [
            {
                'id': 'simulated1',
                'name': '模拟加油站1',
                'lat': 39.9042,
                'lng': 116.4074,
                'brand': '中石化',
                'city': city
            },
            {
                'id': 'simulated2',
                'name': '模拟加油站2',
                'lat': 39.9087,
                'lng': 116.3975,
                'brand': '中石油',
                'city': city
            },
            {
                'id': 'simulated3',
                'name': '模拟加油站3',
                'lat': 39.9123,
                'lng': 116.4021,
                'brand': '壳牌',
                'city': city
            }
        ]

    try:
        API_KEY = '13747fa4ea4d4e3d9fefb637db7b9f21'  # 替换为你的高德API密钥
        url = f"https://restapi.amap.com/v3/place/text?key={API_KEY}&keywords=加油站&city={city}&extensions=all"
        logger.info(f"从高德API获取{city}的加油站数据: {url}")

        response = requests.get(url, timeout=10)
        data = response.json()
        logger.debug(f"API响应: {data}")

        stations = []
        if 'pois' in data:
            for poi in data['pois']:
                location = poi.get('location', '0,0').split(',')
                station = {
                    'id': poi['id'],
                    'name': poi['name'],
                    'lat': float(location[1]) if len(location) > 1 else 0.0,
                    'lng': float(location[0]) if len(location) > 0 else 0.0,
                    'brand': poi.get('brand', '未知'),
                    'city': city
                }
                stations.append(station)

        conn = sqlite3.connect('fuel_data.db')
        for station in stations:
            conn.execute('''INSERT OR REPLACE INTO stations VALUES 
                          (:id, :name, :lat, :lng, :brand, :city)''', station)
        conn.commit()
        conn.close()

        logger.info(f"成功获取{city}的{len(stations)}个加油站")
        return stations

    except Exception as e:
        logger.error(f"获取加油站数据失败: {str(e)}")
        # 回退到模拟数据
        return fetch_gas_stations(city)  # 递归调用会返回模拟数据


def fetch_prices(city, days=30):
    """获取历史油价数据"""
    if SIMULATION_MODE:
        logger.info(f"使用模拟数据获取{city}的油价")
        return []  # 模拟模式下不需要真实数据

    try:
        conn = sqlite3.connect('fuel_data.db')
        c = conn.cursor()

        today = datetime.now().date()
        prices = []

        # 获取该城市所有加油站
        c.execute("SELECT id FROM stations WHERE city=?", (city,))
        station_ids = [row[0] for row in c.fetchall()]

        # 生成模拟数据
        for station_id in station_ids:
            base_price = 7.0 + np.random.uniform(-0.5, 0.5)  # 基准价格

            for i in range(days):
                date = today - timedelta(days=i)

                # 模拟价格波动：工作日+周末效应+随机波动
                weekday = date.weekday()
                price = base_price + (0.1 if weekday >= 5 else 0) + np.random.normal(0, 0.05)

                # 每周四可能涨价
                if weekday == 3 and np.random.rand() > 0.7:
                    price += 0.1

                prices.append({
                    'station_id': station_id,
                    'date': date.isoformat(),
                    'price': round(price, 2)
                })

        # 保存到数据库
        for price in prices:
            conn.execute('''INSERT OR REPLACE INTO prices VALUES 
                          (:station_id, :date, :price)''', price)
        conn.commit()
        conn.close()

        logger.info(f"成功生成{city}的{len(prices)}条油价记录")
        return prices

    except Exception as e:
        logger.error(f"获取油价数据失败: {str(e)}")
        return []


def get_traffic_congestion(origin, destination):
    """获取实时路况数据"""
    if SIMULATION_MODE:
        logger.info(f"使用模拟路况数据: {origin} -> {destination}")
        return np.random.uniform(0.1, 0.5)  # 随机拥堵指数

    try:
        conn = sqlite3.connect('fuel_data.db')
        c = conn.cursor()

        # 生成路线唯一标识
        route_hash = hash(f"{origin}_{destination}")

        # 检查缓存是否有效（5分钟内）
        c.execute('''SELECT congestion_index, timestamp 
                    FROM traffic 
                    WHERE route=? AND timestamp > datetime('now','-5 minutes')''',
                  (str(route_hash),))
        row = c.fetchone()

        if row:
            return row[0]

        # 调用高德API获取实时路况
        API_KEY = '13747fa4ea4d4e3d9fefb637db7b9f21'  # 替换为你的高德API密钥
        url = f"https://restapi.amap.com/v3/direction/driving?key={API_KEY}&origin={origin}&destination={destination}"
        logger.info(f"从高德API获取路况数据: {url}")

        response = requests.get(url, timeout=8)
        data = response.json()
        logger.debug(f"API响应: {data}")

        # 解析拥堵指数
        congestion_index = 0.0
        if 'route' in data and 'paths' in data['route'] and len(data['route']['paths']) > 0:
            path = data['route']['paths'][0]
            if 'congestion_index' in path:
                congestion_index = float(path['congestion_index']) / 10.0

        # 更新缓存
        conn.execute('''INSERT OR REPLACE INTO traffic VALUES (?, ?, datetime('now'))''',
                     (str(route_hash), congestion_index))
        conn.commit()

        logger.info(f"获取路况数据成功: 拥堵指数={congestion_index}")
        return congestion_index

    except Exception as e:
        logger.error(f"获取路况数据失败: {str(e)}")
        return 0.0
    finally:
        if 'conn' in locals():
            conn.close()


# ==================== LSTM油价预测模块 ====================
def prepare_price_data(station_id, look_back=7):
    """为LSTM准备数据"""
    if SIMULATION_MODE:
        logger.info(f"使用模拟价格数据准备LSTM输入: {station_id}")
        # 生成模拟价格序列
        prices = [7.0 + np.random.normal(0, 0.1) for _ in range(30)]
        scaler = MinMaxScaler()
        prices_scaled = scaler.fit_transform(np.array(prices).reshape(-1, 1))

        # 创建训练集
        X, y = [], []
        for i in range(len(prices_scaled) - look_back):
            X.append(prices_scaled[i:i + look_back])
            y.append(prices_scaled[i + look_back])

        return np.array(X), np.array(y), scaler

    try:
        conn = sqlite3.connect('fuel_data.db')
        c = conn.cursor()

        # 获取最近30天价格
        c.execute('''SELECT date, price FROM prices 
                   WHERE station_id=? 
                   ORDER BY date DESC 
                   LIMIT 30''', (station_id,))
        rows = c.fetchall()
        conn.close()

        if not rows or len(rows) < look_back + 1:
            logger.warning(f"加油站{station_id}的历史价格数据不足")
            return None, None, None

        # 转换数据
        prices = [row[1] for row in rows[::-1]]  # 从旧到新
        dates = [row[0] for row in rows[::-1]]

        # 归一化
        scaler = MinMaxScaler()
        prices_scaled = scaler.fit_transform(np.array(prices).reshape(-1, 1))

        # 创建训练集
        X, y = [], []
        for i in range(len(prices_scaled) - look_back):
            X.append(prices_scaled[i:i + look_back])
            y.append(prices_scaled[i + look_back])

        logger.info(f"为加油站{station_id}准备LSTM数据成功")
        return np.array(X), np.array(y), scaler

    except Exception as e:
        logger.error(f"准备价格数据失败: {str(e)}")
        return None, None, None


def train_price_model(station_id):
    """训练LSTM油价预测模型"""
    logger.info(f"训练LSTM模型: {station_id}")
    X, y, scaler = prepare_price_data(station_id)
    if X is None or len(X) == 0:
        logger.warning(f"无法为加油站{station_id}训练模型: 数据不足")
        return None

    try:
        # 保存归一化器
        joblib.dump(scaler, f'models/{station_id}_scaler.pkl')

        # 构建LSTM模型
        model = Sequential([
            LSTM(64, return_sequences=True, input_shape=(X.shape[1], 1)),
            Dropout(0.3),
            LSTM(32, return_sequences=False),
            Dropout(0.3),
            Dense(1)
        ])

        model.compile(optimizer='adam', loss='mean_squared_error')

        # 训练模型
        model.fit(X, y, epochs=150, batch_size=16, verbose=0)

        # 保存模型
        model.save(f'models/{station_id}_lstm.h5')
        logger.info(f"加油站{station_id}的LSTM模型训练完成")
        return model
    except Exception as e:
        logger.error(f"训练模型失败: {str(e)}")
        return None


def predict_future_price(station_id, days=1):
    """预测未来油价"""
    logger.info(f"预测加油站{station_id}的未来油价")

    if SIMULATION_MODE:
        # 模拟预测
        return [7.5 + np.random.uniform(-0.2, 0.2)]

    try:
        # 检查模型是否存在
        model_path = f'models/{station_id}_lstm.h5'
        if not os.path.exists(model_path):
            model = train_price_model(station_id)
            if not model:
                logger.warning(f"无法预测价格: 模型训练失败")
                return [7.5]  # 默认价格
        else:
            model = load_model(model_path)

        # 加载归一化器
        scaler_path = f'models/{station_id}_scaler.pkl'
        if not os.path.exists(scaler_path):
            logger.warning(f"无法预测价格: 归一化器缺失")
            return [7.5]  # 默认价格
        scaler = joblib.load(scaler_path)

        conn = sqlite3.connect('fuel_data.db')
        c = conn.cursor()

        # 获取最新价格数据
        c.execute('''SELECT price FROM prices 
                   WHERE station_id=? 
                   ORDER BY date DESC 
                   LIMIT 7''', (station_id,))
        prices = [row[0] for row in c.fetchall()[::-1]]
        conn.close()

        if len(prices) < 7:
            logger.warning(f"无法预测价格: 历史数据不足")
            return [7.5]  # 默认价格

        # 归一化
        prices_scaled = scaler.transform(np.array(prices).reshape(-1, 1))

        # 预测
        predictions = []
        current_sequence = prices_scaled[-7:].reshape(1, 7, 1)

        for _ in range(days):
            pred = model.predict(current_sequence, verbose=0)[0, 0]
            predictions.append(scaler.inverse_transform([[pred]])[0][0])

            # 更新序列：移除最旧点，添加预测值
            current_sequence = np.append(current_sequence[0][1:], [[pred]]).reshape(1, 7, 1)

        logger.info(f"预测成功: {predictions}")
        return predictions
    except Exception as e:
        logger.error(f"预测价格失败: {str(e)}")
        return [7.5]  # 默认价格


# ==================== 路径优化引擎 ====================
def optimize_fuel_refuel(stations, current_fuel, fuel_efficiency, tank_capacity, time_value, total_distance):
    """
    优化加油策略
    返回：最优加油站索引和加油量
    """
    logger.info("开始优化加油策略")

    if not stations:
        logger.error("优化失败: 无可用加油站")
        return None, None

    # 目标函数：最小化总成本（油费 + 时间成本）
    def total_cost(x):
        station_idx = int(x[0])
        refuel_amt = x[1]

        station = stations[station_idx]
        detour_time = station['detour_time']  # 分钟

        # 油费 = 加油量 * 预测油价
        fuel_cost = refuel_amt * station['predicted_price']

        # 时间成本：绕行时间+加油时间（每分钟成本）
        refuel_time = refuel_amt / 30  # 每分钟加油30升
        time_cost = (detour_time + refuel_time) * (time_value / 60)

        return fuel_cost + time_cost

    # 约束条件
    constraints = [
        # 加油量必须非负
        {'type': 'ineq', 'fun': lambda x: x[1]},

        # 加油量不能超过油箱容量
        {'type': 'ineq', 'fun': lambda x: tank_capacity - current_fuel - x[1]},

        # 加完油后必须能到达目的地或下一站
        {'type': 'ineq', 'fun': lambda x:
        current_fuel + x[1] - (stations[int(x[0])]['distance_from_route'] * fuel_efficiency / 100 + 5)}
    ]

    # 初始值（第一个加油站，加油10升）
    x0 = [0, 10]

    # 边界：加油站索引范围，加油量范围
    bounds = [(0, len(stations) - 1), (0, tank_capacity)]

    # 优化
    try:
        result = minimize(total_cost, x0, method='SLSQP', bounds=bounds, constraints=constraints)

        if result.success:
            station_idx = int(result.x[0])
            refuel_amt = result.x[1]
            logger.info(f"优化成功: 选择加油站{station_idx}, 加油量{refuel_amt:.2f}L")
            return station_idx, refuel_amt
        else:
            logger.warning(f"优化失败: {result.message}")
    except Exception as e:
        logger.error(f"优化过程中出错: {str(e)}")

    # 回退策略：选择最近的加油站加半箱油
    logger.info("使用回退策略")
    min_distance = min(station['distance_from_route'] for station in stations)
    for i, station in enumerate(stations):
        if station['distance_from_route'] <= min_distance + 1.0:
            refuel_amt = min((tank_capacity - current_fuel) / 2,
                             total_distance * fuel_efficiency / 100 - current_fuel)
            refuel_amt = max(refuel_amt, 0)
            logger.info(f"回退策略: 选择加油站{i}, 加油量{refuel_amt:.2f}L")
            return i, refuel_amt

    # 最终回退
    refuel_amt = min((tank_capacity - current_fuel) / 2, total_distance * fuel_efficiency / 100 - current_fuel)
    refuel_amt = max(refuel_amt, 0)
    logger.info(f"最终回退: 选择加油站0, 加油量{refuel_amt:.2f}L")
    return 0, refuel_amt


# ==================== 系统集成API ====================
@app.route('/optimize-route', methods=['POST'])
def optimize_route():
    """API接口：优化路线推荐"""
    logger.info("收到优化请求")

    try:
        data = request.json
        logger.debug(f"请求数据: {data}")

        # 解析参数
        required_fields = ['start', 'end', 'current_fuel', 'fuel_efficiency', 'tank_capacity']
        for field in required_fields:
            if field not in data:
                logger.error(f"缺少必要字段: {field}")
                return jsonify({'error': f'缺少必要字段: {field}'}), 400

        start = data['start']
        end = data['end']
        current_fuel = float(data['current_fuel'])
        fuel_efficiency = float(data['fuel_efficiency'])
        tank_capacity = float(data['tank_capacity'])
        time_value = float(data.get('time_value', 100))  # 默认100元/小时
        city = data.get('city', '北京')

        # 1. 获取实时路况
        congestion = get_traffic_congestion(start, end)
        logger.info(f"路况拥堵指数: {congestion:.2f}")

        # 2. 获取路径信息
        route_info = simulate_route_data(start, end, congestion)
        total_distance = route_info['total_distance']
        logger.info(f"路线距离: {total_distance}公里")

        # 3. 获取加油站数据
        stations = fetch_gas_stations(city)
        logger.info(f"获取到{len(stations)}个加油站")

        if not stations:
            logger.error("无法获取加油站数据")
            return jsonify({'error': '无法获取加油站数据'}), 400

        # 4. 为每个加油站添加位置和预测价格
        for station in stations:
            # 模拟路径距离
            station['distance_from_route'] = np.random.uniform(0.5, 5)  # 公里
            station['detour_time'] = station['distance_from_route'] * 2  # 分钟（包含出入时间）

            # 预测价格
            try:
                predicted_price = predict_future_price(station['id'])
                station['predicted_price'] = predicted_price[0] if predicted_price else 7.5
                logger.debug(f"加油站 {station['name']} 预测价格: {station['predicted_price']}")
            except Exception as e:
                logger.error(f"预测价格失败: {str(e)}")
                station['predicted_price'] = 7.5

        # 5. 优化加油策略
        station_idx, refuel_amt = optimize_fuel_refuel(
            stations, current_fuel, fuel_efficiency, tank_capacity, time_value, total_distance
        )

        if station_idx is None or refuel_amt is None:
            logger.error("优化失败，无合适加油站")
            return jsonify({'error': '优化失败，无合适加油站'}), 400

        optimal_station = stations[station_idx]

        # 计算节省成本
        max_price = max(s['predicted_price'] for s in stations)
        savings = max_price * refuel_amt - optimal_station['predicted_price'] * refuel_amt

        # 返回结果
        result = {
            'optimal_station': optimal_station['name'],
            'station_id': optimal_station['id'],
            'station_brand': optimal_station['brand'],
            'refuel_amount': round(refuel_amt, 1),
            'predicted_price': optimal_station['predicted_price'],
            'detour_distance': round(optimal_station['distance_from_route'], 2),
            'detour_time': round(optimal_station['detour_time'], 1),
            'fuel_cost': round(refuel_amt * optimal_station['predicted_price'], 2),
            'time_cost': round(optimal_station['detour_time'] * (time_value / 60), 2),
            'total_cost': round(
                refuel_amt * optimal_station['predicted_price'] +
                optimal_station['detour_time'] * (time_value / 60),
                2
            ),
            'savings': round(savings, 2),
            'total_distance': total_distance,
            'estimated_time': round(total_distance / (40 * (1 - congestion)) * 60, 1)  # 分钟
        }

        logger.info(f"返回优化结果: {result}")
        return jsonify(result)

    except Exception as e:
        logger.error(f"处理请求时出错: {str(e)}")
        return jsonify({'error': '服务器内部错误'}), 500


def simulate_route_data(start, end, congestion):
    """模拟路径数据"""
    # 模拟距离（基于北京主要地点）
    known_routes = {
        ("天安门", "故宫"): 5.2,
        ("天安门", "颐和园"): 25.3,
        ("中关村", "首都机场"): 35.8,
        ("西单", "北京南站"): 8.7
    }

    distance = known_routes.get((start, end))
    if not distance:
        # 随机生成距离（10-100公里）
        distance = np.random.uniform(10, 100)

    return {
        'start': start,
        'end': end,
        'total_distance': round(distance * (1 + congestion * 0.3), 1),  # 拥堵增加距离
        'congestion_level': congestion
    }


# ==================== 初始化系统 ====================
def initialize_system():
    """系统初始化"""
    logger.info("正在初始化系统...")

    try:
        # 创建目录
        os.makedirs('models', exist_ok=True)
        logger.info("创建models目录成功")

        # 设置数据库
        setup_database()

        # 启动后台数据刷新线程
        threading.Thread(target=data_refresh_daemon, daemon=True).start()
        logger.info("系统初始化完成")
    except Exception as e:
        logger.error(f"系统初始化失败: {str(e)}")


def data_refresh_daemon():
    """后台数据刷新守护进程"""
    import time
    while True:
        try:
            logger.info("开始日常数据刷新...")

            # 刷新大城市加油站数据
            for city in ['北京', '上海', '广州', '深圳']:
                fetch_gas_stations(city)

            # 刷新油价数据
            for city in ['北京', '上海', '广州', '深圳']:
                fetch_prices(city)

            logger.info("数据刷新完成")

        except Exception as e:
            logger.error(f"数据刷新出错: {str(e)}")

        # 每6小时刷新一次
        time.sleep(6 * 60 * 60)


if __name__ == '__main__':
    # 解决Windows系统上的Flask多线程问题
    import platform

    if platform.system() == "Windows":
        from threading import Thread

        Thread(target=initialize_system).start()
    else:
        initialize_system()

    app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)