import logging
import os
import secrets

from cryptography.fernet import Fernet
from dotenv import load_dotenv
from sqlalchemy.engine import URL

load_dotenv()

log = logging.getLogger(__name__)

SECRET_SETTINGS = ("SECRET_KEY", "API_KEY", "TIKTOK_CLIENT_SECRET")
KNOWN_SECRET_PLACEHOLDERS = {"dev-secret-change-me"}

SUPPORTED_DB_ENGINES = ("sqlite", "postgres")
DEFAULT_DATABASE_PATH = "/app/data/tiktok_manager.db"
PLACEHOLDER_PASSWORDS = {"", "postgres", "password", "secret", "changeme", "change-me", "your-password"}


def is_placeholder_secret(value):
    """Return True when a secret value is empty, a well-known default or a change-me placeholder."""
    v = (value or "").strip().lower()
    return v in PLACEHOLDER_PASSWORDS or v.startswith("change-me") or v.startswith("your-")


def database_uri(env=None):
    """Build the SQLAlchemy URL from DATABASE_URL, else DB_ENGINE + DATABASE_PATH / POSTGRES_*; raises ValueError on an unknown engine."""
    env = os.environ if env is None else env
    override = (env.get("DATABASE_URL") or "").strip()
    if override:
        return override
    engine = (env.get("DB_ENGINE") or "sqlite").strip().lower()
    if engine == "sqlite":
        return f"sqlite:///{env.get('DATABASE_PATH') or DEFAULT_DATABASE_PATH}"
    if engine == "postgres":
        return URL.create(
            "postgresql+psycopg",
            username=env.get("POSTGRES_USER") or "tiktok_manager",
            password=env.get("POSTGRES_PASSWORD") or None,
            host=env.get("POSTGRES_HOST") or "db",
            port=int(env.get("POSTGRES_PORT") or 5432),
            database=env.get("POSTGRES_DB") or "tiktok_manager",
        ).render_as_string(hide_password=False)
    raise ValueError(f"DB_ENGINE must be one of {SUPPORTED_DB_ENGINES}, got {engine!r}")


def is_placeholder_app_secret(value):
    """Return True when an application secret is empty, a shipped default or a change-me placeholder."""
    v = (value or "").strip().lower()
    return is_placeholder_secret(v) or v in KNOWN_SECRET_PLACEHOLDERS or "change-me" in v


def token_encryption_keys(value):
    """Split TOKEN_ENCRYPTION_KEY (comma-separated Fernet keys; the first encrypts, all decrypt) into a list; raises ValueError when empty or malformed."""
    keys = [k.strip() for k in (value or "").split(",") if k.strip()]
    if not keys:
        raise ValueError("is not set")
    for k in keys:
        try:
            Fernet(k)
        except (ValueError, TypeError):
            raise ValueError("contains a value that is not a Fernet key") from None
    return keys


KEY_HINT = ('generate one with: python -c "from cryptography.fernet import Fernet; '
            'print(Fernet.generate_key().decode())"')


def check_secrets_config(config, env=None):
    """Fail closed at startup: in production every application secret must be set and real;
    outside production, mint a random SECRET_KEY when none is set. Raises RuntimeError that names
    the offending variables and never their values."""
    env = os.environ if env is None else env
    production = config.get("FLASK_ENV", "production") == "production"
    try:
        token_encryption_keys(config.get("TOKEN_ENCRYPTION_KEY"))
        key_error = None
    except ValueError as e:
        key_error = str(e)

    if production:
        missing = [name for name in SECRET_SETTINGS if is_placeholder_app_secret(config.get(name))]
        if missing or key_error:
            raise RuntimeError(
                "Refusing to start with FLASK_ENV=production: "
                + ", ".join(missing + (["TOKEN_ENCRYPTION_KEY"] if key_error else []))
                + " missing or still a placeholder from .env.example"
                + (f"; TOKEN_ENCRYPTION_KEY {key_error}, {KEY_HINT}" if key_error else "")
            )
        return

    if key_error:
        raise RuntimeError(f"Refusing to start: TOKEN_ENCRYPTION_KEY {key_error}; {KEY_HINT}")
    if is_placeholder_app_secret(config.get("SECRET_KEY")):
        config["SECRET_KEY"] = secrets.token_hex(32)
        log.warning("SECRET_KEY not set: generated a random key for this process; sessions reset on restart")


def check_access_config(config):
    """Refuse to start in production without TIKTOK_ALLOWED_ACCOUNTS: the sign-in allowlist must be
    a list of TikTok open_id / union_id values, or "*" to let any TikTok account sign in; raises RuntimeError."""
    if config.get("FLASK_ENV", "production") != "production":
        return
    if not config.get("TIKTOK_ALLOWED_ACCOUNTS"):
        raise RuntimeError(
            "TIKTOK_ALLOWED_ACCOUNTS is empty: list the TikTok open_id/union_id values allowed to sign in, "
            "or set it to * to allow any TikTok account"
        )


def check_database_config(config, env=None):
    """Refuse to start in production with DB_ENGINE=postgres and a missing or placeholder POSTGRES_PASSWORD; raises RuntimeError."""
    env = os.environ if env is None else env
    production = config.get("FLASK_ENV", "production") == "production"
    engine = (config.get("DB_ENGINE") or "sqlite").strip().lower()
    if engine not in SUPPORTED_DB_ENGINES:
        raise ValueError(f"DB_ENGINE must be one of {SUPPORTED_DB_ENGINES}, got {engine!r}")
    if not production or engine != "postgres" or (env.get("DATABASE_URL") or "").strip():
        return
    if is_placeholder_secret(env.get("POSTGRES_PASSWORD")):
        raise RuntimeError(
            "DB_ENGINE=postgres requires a real POSTGRES_PASSWORD in production; "
            "it is missing or a placeholder"
        )


class Config:
    SECRET_KEY = os.environ.get("SECRET_KEY", "")
    FLASK_ENV = os.environ.get("FLASK_ENV", "production")

    TIKTOK_CLIENT_KEY = os.environ.get("TIKTOK_CLIENT_KEY", "")
    TIKTOK_CLIENT_SECRET = os.environ.get("TIKTOK_CLIENT_SECRET", "")
    TIKTOK_REDIRECT_URI = os.environ.get(
        "TIKTOK_REDIRECT_URI", "https://localhost:9506/auth/callback"
    )
    TIKTOK_SCOPES = os.environ.get(
        "TIKTOK_SCOPES",
        "user.info.basic,user.info.stats,video.list,video.upload",
    ).split(",")
    TIKTOK_ALLOWED_ACCOUNTS = [
        s.strip() for s in os.environ.get("TIKTOK_ALLOWED_ACCOUNTS", "").split(",") if s.strip()
    ]

    DB_ENGINE = os.environ.get("DB_ENGINE", "sqlite").strip().lower()
    DATABASE_PATH = os.environ.get("DATABASE_PATH", DEFAULT_DATABASE_PATH)
    SQLALCHEMY_DATABASE_URI = database_uri()
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    DB_CONNECT_TIMEOUT_SECONDS = int(os.environ.get("DB_CONNECT_TIMEOUT_SECONDS", "60"))

    MAX_UPLOAD_SIZE_MB = int(os.environ.get("MAX_UPLOAD_SIZE_MB", "4096"))
    MAX_CONTENT_LENGTH = MAX_UPLOAD_SIZE_MB * 1024 * 1024

    MEDIA_FOLDER = os.environ.get("MEDIA_FOLDER", "/app/media")
    SCHEDULER_INTERVAL_SECONDS = int(os.environ.get("SCHEDULER_INTERVAL_SECONDS", "30"))

    APP_NAME = os.environ.get("APP_NAME", "TikTok Manager")
    UPLOAD_FOLDER = os.environ.get("UPLOAD_FOLDER", "/app/uploads")

    API_KEY = os.environ.get("API_KEY", "")
    TOKEN_ENCRYPTION_KEY = os.environ.get("TOKEN_ENCRYPTION_KEY", "")

    WTF_CSRF_TIME_LIMIT = 12 * 60 * 60
    SESSION_COOKIE_SAMESITE = "Lax"
    SESSION_COOKIE_SECURE = FLASK_ENV == "production"
