#!/usr/bin/env python3
"""Upload one directory as a configuration set, then synchronize it."""

import argparse
import os
import sys
from pathlib import Path

import requests


SUPPORTED_SUFFIXES = {".json", ".jsonc", ".yaml", ".yml"}
REQUEST_TIMEOUT = 30


class ConfigSetError(Exception):
    pass


def request(session, method, url, **kwargs):
    try:
        response = session.request(method, url, timeout=REQUEST_TIMEOUT, **kwargs)
    except requests.RequestException as error:
        raise ConfigSetError(f"请求失败: {error}") from error
    return response


def error_detail(response):
    try:
        data = response.json()
    except ValueError:
        return response.text.strip() or response.reason
    if isinstance(data, dict):
        return data.get("detail", data)
    return data


def require_success(response):
    if not response.ok:
        raise ConfigSetError(
            f"HTTP {response.status_code}: {error_detail(response)}"
        )
    return response


def find_config_files(directory):
    files = sorted(
        path
        for path in directory.iterdir()
        if path.is_file()
        and not path.name.startswith(".")
        and path.suffix.lower() in SUPPORTED_SUFFIXES
    )
    if not files:
        raise ConfigSetError(f"目录中没有支持的配置文件: {directory}")
    return files


def get_or_create_config_set(session, base_url, name):
    response = require_success(
        request(
            session,
            "GET",
            f"{base_url}/system/config_set/",
            params={"f_name": name},
        )
    )
    results = response.json()["results"]
    if results:
        config_set = results[0]
        print(f"复用配置集合: {name} (ID {config_set['id']})")
        return config_set

    response = require_success(
        request(
            session,
            "POST",
            f"{base_url}/system/config_set/",
            json={"name": name, "describe": "Jenkins 目录上传", "type": 1},
        )
    )
    config_set = response.json()
    print(f"创建配置集合: {name} (ID {config_set['id']})")
    return config_set


def upload_files(session, base_url, config_set_id, files):
    url = f"{base_url}/system/config_set/{config_set_id}/resource/"
    for path in files:
        print(f"上传: {path.name}")
        with path.open("rb") as stream:
            require_success(
                request(session, "POST", url, files={"file": (path.name, stream)})
            )


def sync_config_set(session, base_url, config_set_id):
    sync_url = f"{base_url}/system/config_set/{config_set_id}/sync/"
    require_success(request(session, "POST", sync_url))

    detail_url = f"{base_url}/system/config_set/{config_set_id}/"
    config_set = require_success(request(session, "GET", detail_url)).json()
    status_message = str(config_set.get("status_message", ""))
    if config_set.get("status") != 1 or not status_message.startswith("同步成功"):
        raise ConfigSetError(status_message or "配置集合同步失败")
    print(status_message)


def parse_args():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("directory", type=Path, help="配置文件所在目录")
    parser.add_argument("--name", help="配置集合名称，默认使用目录名")
    return parser.parse_args()


def main():
    args = parse_args()
    directory = args.directory.expanduser().resolve()
    if not directory.is_dir():
        raise ConfigSetError(f"目录不存在: {directory}")

    base_url = os.environ.get("NORMAE_BASE_URL", "").rstrip("/")
    token = os.environ.get("NORMAE_TOKEN", "")
    if not base_url or not token:
        raise ConfigSetError("请设置 NORMAE_BASE_URL 和 NORMAE_TOKEN")

    files = find_config_files(directory)
    name = args.name or directory.name
    with requests.Session() as session:
        session.headers["X-NORMAE-AUTH-TOKEN"] = token
        config_set = get_or_create_config_set(session, base_url, name)
        upload_files(session, base_url, config_set["id"], files)
        sync_config_set(session, base_url, config_set["id"])


if __name__ == "__main__":
    try:
        main()
    except (ConfigSetError, OSError, ValueError) as error:
        print(f"错误: {error}", file=sys.stderr)
        sys.exit(1)
