# _*_ coding : UTF-8 _*_
# @Time : 2025/01/17 22:18
# @UpdateTime : 2025/01/17 22:18
# @Author : sonder
# @File : env.py
# @Software : PyCharm
# @Comment : 本程序
import argparse
import os
import sys
from functools import lru_cache

from dotenv import load_dotenv
from pydantic_settings import BaseSettings


class AppSettings(BaseSettings):
    """
    应用配置类，用于管理 FastAPI 应用的基本设置。
    """

    app_env: str = 'dev'
    """
    应用环境，默认为 'dev'。
    可选值：
    - 'dev': 开发环境
    - 'prod': 生产环境
    """

    app_name: str = 'FasAPI'
    """
    应用名称，默认为 'FasAPI'。
    用于标识当前应用的名称。
    """

    app_root_path: str = '/dev-api'
    """
    应用的根路径，默认为 '/dev-api'。
    用于设置 API 的根路径前缀。
    """

    app_host: str = '0.0.0.0'
    """
    应用绑定的主机地址，默认为 '0.0.0.0'。
    - '0.0.0.0' 表示监听所有网络接口。
    - '127.0.0.1' 表示仅监听本地回环地址。
    """

    app_port: int = 9090
    """
    应用绑定的端口号，默认为 9090。
    确保端口号未被其他服务占用。
    """

    app_version: str = '1.0.0'
    """
    应用版本号，默认为 '1.0.0'。
    用于标识当前应用的版本。
    """

    app_reload: bool = True
    """
    是否启用自动重载，默认为 True。
    - True: 启用自动重载，适合开发环境。
    - False: 禁用自动重载，适合生产环境。
    """

    app_ip_location_query: bool = True
    """
    是否启用 IP 地址地理位置查询，默认为 True。
    - True: 启用 IP 地址地理位置查询功能。
    - False: 禁用 IP 地址地理位置查询功能。
    """

    app_same_time_login: bool = True
    """
    是否允许同一用户同时登录多个设备，默认为 True。
    - True: 允许同一用户同时登录多个设备。
    - False: 禁止同一用户同时登录多个设备。
    """

    class Config:
        env_file_encoding = "utf-8"  # 指定文件编码


class JwtSettings(BaseSettings):
    """
    JWT 配置类，用于管理 JWT（JSON Web Token）相关的设置。
    """

    jwt_secret_key: str = 'b01c66dc2c58dc6a0aabfe2144256be36226de378bf87f72c0c795dda67f4d55'
    """
    JWT 签名密钥，默认为一个随机生成的字符串。
    - 用于加密和解密 JWT 令牌。
    - 生产环境中应使用更安全的密钥，并妥善保管。
    """

    jwt_algorithm: str = 'HS256'
    """
    JWT 签名算法，默认为 'HS256'。
    - 支持的算法包括：
      - 'HS256': HMAC + SHA-256
      - 'HS384': HMAC + SHA-384
      - 'HS512': HMAC + SHA-512
      - 'RS256': RSA + SHA-256
      - 其他算法请参考 PyJWT 文档。
    """

    jwt_salt: str = 'jwt_salt'
    """
    JWT 盐值，默认为 'jwt_salt'。
    - 用于进一步增强 JWT 的安全性，防止彩虹表攻击。
    - 生产环境中应使用更复杂的盐值。
    """

    jwt_expire_minutes: int = 1440
    """
    JWT 令牌的有效期（以分钟为单位），默认为 1440 分钟（24 小时）。
    - 超过有效期后，令牌将自动失效。
    - 可以根据需求调整有效期。
    """

    jwt_redis_expire_minutes: int = 30
    """
    JWT 令牌在 Redis 中的缓存有效期（以分钟为单位），默认为 30 分钟。
    - 用于存储已签发的 JWT 令牌，以便快速验证和吊销。
    - 可以根据需求调整缓存有效期。
    """

    class Config:
        env_file_encoding = "utf-8"  # 指定文件编码


class DataBaseSettings(BaseSettings):
    """
    数据库配置类，用于管理数据库连接相关的设置。
    """

    db_type: str = 'mysql'
    """
    数据库类型，默认为 'mysql'。
    - 支持的数据库类型包括：
      - 'mysql': MySQL 数据库
      - 'postgresql': PostgreSQL 数据库
      - 'sqlite': SQLite 数据库
    """

    db_host: str = '127.0.0.1'
    """
    数据库主机地址，默认为 '127.0.0.1'。
    - 如果数据库运行在本地，可以使用 'localhost' 或 '127.0.0.1'。
    - 如果数据库运行在远程服务器，请填写服务器的 IP 地址或域名。
    """

    db_port: int = 3306
    """
    数据库端口号，默认为 3306。
    - MySQL 默认端口号为 3306。
    - PostgreSQL 默认端口号为 5432。
    - 根据实际数据库类型和配置调整端口号。
    """

    db_username: str = 'root'
    """
    数据库用户名，默认为 'root'。
    - 用于连接数据库的用户名。
    - 生产环境中应使用具有最小权限的用户。
    """

    db_password: str = 'mysqlroot'
    """
    数据库密码，默认为 'mysqlroot'。
    - 用于连接数据库的密码。
    - 生产环境中应使用强密码，并妥善保管。
    """

    db_database: str = 'fastapi'
    """
    数据库名称，默认为 'fastapi'。
    - 用于指定连接的数据库名称。
    - 确保数据库已创建并具有相应的权限。
    """

    db_echo: bool = True
    """
    是否打印 SQL 语句，默认为 True。
    - True: 打印所有执行的 SQL 语句，适合开发和调试。
    - False: 不打印 SQL 语句，适合生产环境。
    """

    db_max_overflow: int = 10
    """
    连接池最大溢出连接数，默认为 10。
    - 当连接池中的连接数达到最大值时，允许额外创建的连接数。
    - 根据实际负载调整该值。
    """

    db_log_level: int = 10
    """
    数据库日志级别，默认为 10（DEBUG）。
    - 用于控制数据库日志的输出级别。
    - 10: DEBUG
    - 20: INFO
    - 30: WARNING
    - 40: ERROR
    - 50: CRITICAL
    - 根据实际需求调整日志级别。
    """

    db_pool_size: int = 50
    """
    连接池大小，默认为 50。
    - 连接池中保持的最大连接数。
    - 根据实际并发需求调整该值。
    """

    db_pool_recycle: int = 3600
    """
    连接池回收时间（秒），默认为 3600 秒（1 小时）。
    - 超过该时间的连接会被回收并重新创建。
    - 用于避免数据库连接超时问题。
    """

    db_pool_timeout: int = 30
    """
    连接池超时时间（秒），默认为 30 秒。
    - 从连接池获取连接的超时时间。
    - 如果超时未获取到连接，会抛出异常。
    """

    class Config:
        env_file_encoding = "utf-8"  # 指定文件编码


class RedisSettings(BaseSettings):
    """
    Redis 配置类，用于管理 Redis 连接相关的设置。
    """

    redis_host: str = '127.0.0.1'
    """
    Redis 主机地址，默认为 '127.0.0.1'。
    - 如果 Redis 运行在本地，可以使用 'localhost' 或 '127.0.0.1'。
    - 如果 Redis 运行在远程服务器，请填写服务器的 IP 地址或域名。
    """

    redis_port: int = 6379
    """
    Redis 端口号，默认为 6379。
    - Redis 默认端口号为 6379。
    - 根据实际 Redis 配置调整端口号。
    """

    redis_username: str = ''
    """
    Redis 用户名，默认为空。
    - 如果 Redis 启用了身份验证，请填写用户名。
    - 如果未启用身份验证，可以留空。
    """

    redis_password: str = ''
    """
    Redis 密码，默认为空。
    - 如果 Redis 启用了身份验证，请填写密码。
    - 如果未启用身份验证，可以留空。
    """

    redis_database: int = 2
    """
    Redis 数据库索引，默认为 2。
    - Redis 支持多个数据库，索引从 0 开始。
    - 根据实际需求选择合适的数据库索引。
    """

    class Config:
        env_file_encoding = "utf-8"  # 指定文件编码


class UploadSettings:
    """
    上传配置类，用于管理文件上传和下载的相关设置。
    """

    UPLOAD_PREFIX = '/profile'
    """
    文件上传的 URL 前缀，默认为 '/profile'。
    - 用于在访问上传文件时添加到 URL 的前缀。
    - 例如：`/profile/example.jpg`。
    """

    UPLOAD_PATH = 'data/upload_path'
    """
    文件上传的存储路径，默认为 'data/upload_path'。
    - 上传的文件将存储在此目录中。
    - 如果目录不存在，会自动创建。
    """

    UPLOAD_MACHINE = 'A'
    """
    上传机器的标识，默认为 'A'。
    - 用于区分不同的上传机器或节点。
    - 在多机部署时可以使用此字段。
    """

    DEFAULT_ALLOWED_EXTENSION = [
        # 图片
        'bmp',
        'gif',
        'jpg',
        'jpeg',
        'png',
        # word excel powerpoint
        'doc',
        'docx',
        'xls',
        'xlsx',
        'ppt',
        'pptx',
        'html',
        'htm',
        'txt',
        # 压缩文件
        'rar',
        'zip',
        'gz',
        'bz2',
        # 视频格式
        'mp4',
        'avi',
        'rmvb',
        # pdf
        'pdf',
    ]
    """
    默认允许上传的文件扩展名列表。
    - 包含常见的图片、文档、压缩文件、视频和 PDF 格式。
    - 可以根据需求扩展或修改此列表。
    """

    DOWNLOAD_PATH = 'data/download_path'
    """
    文件下载的存储路径，默认为 'data/download_path'。
    - 下载的文件将存储在此目录中。
    - 如果目录不存在，会自动创建。
    """

    def __init__(self):
        """
        初始化方法，确保上传和下载路径存在。
        - 如果路径不存在，会自动创建。
        """
        if not os.path.exists(self.UPLOAD_PATH):
            os.makedirs(self.UPLOAD_PATH)
        if not os.path.exists(self.DOWNLOAD_PATH):
            os.makedirs(self.DOWNLOAD_PATH)


class EmailSettings(BaseSettings):
    """
    邮件配置类，用于管理邮件发送相关的设置。
    """
    email_username: str = ""
    """
    邮件发送者的用户名，默认为空。
    """
    email_password: str = ""
    """
    邮件发送者的密码，默认为空。
    """
    email_host: str = "smtp.qq.com"
    """
    邮件服务器地址，默认为 "smtp.qq.com"。
    """
    email_port: int = 465
    """
    邮件服务器端口，默认为 465。
    """

    class Config:
        env_file_encoding = "utf-8"  # 指定文件编码


class MapSettings(BaseSettings):
    """
    地图配置类，用于管理地图相关的设置。
    """
    ak: str = ""
    """
    控制台-应用管理-创建应用后获取的AK
    """
    sk: str = ""
    """
    控制台-应用管理-创建应用时，校验方式选择sn校验后生成的SK
    """


class CachePathConfig:
    """
    缓存目录配置
    """

    PATH = os.path.join(os.path.abspath(os.getcwd()), 'caches')
    PATHSTR = 'caches'


class GetConfig:
    """
    获取配置类，用于集中管理和获取应用的所有配置。
    """

    def __init__(self):
        """
        初始化方法，解析命令行参数并加载环境变量。
        """
        self.parse_cli_args()

    @lru_cache()
    def get_app_config(self) -> BaseSettings:
        """
        获取应用配置。
        - 返回 AppSettings 的实例。
        - 使用 lru_cache 缓存结果，避免重复实例化。
        """
        # 实例化应用配置模型
        return AppSettings()

    @lru_cache()
    def get_jwt_config(self) -> BaseSettings:
        """
        获取 JWT 配置。
        - 返回 JwtSettings 的实例。
        - 使用 lru_cache 缓存结果，避免重复实例化。
        """
        # 实例化 JWT 配置模型
        return JwtSettings()

    @lru_cache()
    def get_database_config(self) -> BaseSettings:
        """
        获取数据库配置。
        - 返回 DataBaseSettings 的实例。
        - 使用 lru_cache 缓存结果，避免重复实例化。
        """
        # 实例化数据库配置模型
        return DataBaseSettings()

    @lru_cache()
    def get_redis_config(self) -> BaseSettings:
        """
        获取 Redis 配置。
        - 返回 RedisSettings 的实例。
        - 使用 lru_cache 缓存结果，避免重复实例化。
        """
        # 实例化 Redis 配置模型
        return RedisSettings()

    @lru_cache()
    def get_upload_config(self) -> 'UploadSettings':
        """
        获取上传配置。
        - 返回 UploadSettings 的实例。
        - 使用 lru_cache 缓存结果，避免重复实例化。
        """
        # 实例化上传配置
        return UploadSettings()

    @lru_cache()
    def get_email_config(self) -> 'EmailSettings':
        """
        获取邮件配置。
        - 返回 EmailSettings 的实例。
        - 使用 lru_cache 缓存结果，避免重复实例化。
        """
        # 实例化邮件配置
        return EmailSettings()

    @lru_cache()
    def get_map_config(self) -> 'MapSettings':
        """
        获取地图配置。
        - 返回 MapSettings 的实例。
        - 使用 lru_cache 缓存结果，避免重复实例化。
        """
        # 实例化地图配置
        return MapSettings()

    @staticmethod
    def parse_cli_args():
        """
        解析命令行参数。
        - 如果使用 uvicorn 启动，命令行参数无法自定义。
        - 否则，使用 argparse 解析自定义命令行参数。
        - 根据命令行参数设置环境变量，并加载对应的 .env 文件。
        """
        if 'uvicorn' in sys.argv[0]:
            # 使用 uvicorn 启动时，命令行参数需要按照 uvicorn 的文档进行配置，无法自定义参数
            pass
        else:
            # 使用 argparse 定义命令行参数
            parser = argparse.ArgumentParser(description='命令行参数')
            parser.add_argument('--env', type=str, default='', help='运行环境')
            # 解析命令行参数
            args = parser.parse_args()
            # 设置环境变量，如果未设置命令行参数，默认 APP_ENV 为 dev
            os.environ['APP_ENV'] = args.env if args.env else 'dev'

        # 读取运行环境
        run_env = os.environ.get('APP_ENV', '')
        # 运行环境未指定时默认加载 .env.dev
        env_file = '.env.dev'
        # 运行环境不为空时按命令行参数加载对应 .env 文件
        if run_env != '':
            env_file = f'.env.{run_env}'
        # 加载配置
        load_dotenv(env_file)


# 实例化获取配置类
get_config = GetConfig()

# 应用配置
AppConfig = get_config.get_app_config()

# JWT 配置
JwtConfig = get_config.get_jwt_config()

# 数据库配置
DataBaseConfig = get_config.get_database_config()

# Redis 配置
RedisConfig = get_config.get_redis_config()

# 上传配置
UploadConfig = get_config.get_upload_config()

# 邮件配置
EmailConfig = get_config.get_email_config()

# 地图配置
MapConfig = get_config.get_map_config()
