#!/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:
        timeout = kwargs.pop("timeout", REQUEST_TIMEOUT)
        response = session.request(method, url, timeout=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 check_tenant(session, base_url, expected_id):
    current = require_success(
        request(session, "GET", f"{base_url}/system/tenant/current/")
    ).json()
    if current.get("tenant_id") != expected_id:
        raise ConfigSetError(
            f"租户不匹配: 期望 {expected_id}，Token 实际属于 "
            f"{current.get('tenant_id')}；未执行写入"
        )
    print(f"租户校验通过: {expected_id}")


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:
        if len(results) != 1 or results[0].get("name") != name:
            raise ConfigSetError("配置集合名称查询结果不唯一或不匹配")
        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, timeout=300):
    sync_url = f"{base_url}/system/config_set/{config_set_id}/sync/"
    require_success(request(session, "POST", sync_url, timeout=timeout))

    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="配置集合名称，默认使用目录名")
    parser.add_argument("--tenant-id", type=int, help="写入前校验 Token 所属租户 ID")
    parser.add_argument("--sync-timeout", type=float, default=300,
                        help="同步请求超时秒数，默认300")
    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")
    if args.sync_timeout <= 0 or (args.tenant_id is not None and args.tenant_id <= 0):
        raise ConfigSetError("租户 ID 和超时时间必须为正数")

    files = find_config_files(directory)
    name = args.name or directory.name
    with requests.Session() as session:
        session.headers["X-NORMAE-AUTH-TOKEN"] = token
        if args.tenant_id is not None:
            check_tenant(session, base_url, args.tenant_id)
        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"], args.sync_timeout)


if __name__ == "__main__":
    try:
        main()
    except (ConfigSetError, OSError, ValueError) as error:
        token = os.environ.get("NORMAE_TOKEN", "")
        message = str(error).replace(token, "[已隐藏]") if token else str(error)
        print(f"错误: {message}", file=sys.stderr)
        sys.exit(1)
