#!/usr/bin/env python3
"""Upload a static archive or verify its tenant, engine sync and served release."""

import argparse
import hashlib
import os
import sys
import time
from pathlib import Path
from urllib.parse import urlsplit

import requests

from config_set_sync import ConfigSetError, check_tenant, request, require_success


def find_resource(session, base_url, name):
    data = require_success(request(
        session, "GET", f"{base_url}/slb/static_resource/", params={"f_name": name}
    )).json()
    items = data["results"]
    if not items:
        return None
    if len(items) != 1 or items[0].get("name") != name:
        raise ConfigSetError("静态资源名称查询结果不唯一或不匹配")
    return items[0]


def get_detail(session, base_url, resource_id, timeout=30):
    return require_success(request(
        session, "GET", f"{base_url}/slb/static_resource/{resource_id}/",
        timeout=timeout,
    )).json()


def upload(session, base_url, args):
    archive = args.archive.expanduser().resolve()
    if not archive.is_file() or not archive.name.endswith((".zip", ".tar.gz")):
        raise ConfigSetError("请选择存在的 .zip 或 .tar.gz 压缩包")
    resource = find_resource(session, base_url, args.name)
    if resource is None:
        url = f"{base_url}/slb/static_resource/"
        data = {"name": args.name}
        action = "创建"
    else:
        detail = get_detail(session, base_url, resource["id"])
        if detail.get("static_type") != 1:
            raise ConfigSetError("同名资源是目录映射类型，请按目录映射发布流程操作")
        if detail.get("upload_status") != 0 or any(
            item.get("status") != 1 for item in detail.get("sync_status_list", [])
        ):
            raise ConfigSetError("静态资源仍在上传或同步，请等待上一发布完成")
        url = f"{base_url}/slb/static_resource/{resource['id']}/tree/"
        data = {}
        action = "替换"
    with archive.open("rb") as stream:
        response = require_success(request(
            session, "POST", url, data=data,
            files={"file": (archive.name, stream)}, timeout=args.upload_timeout,
        ))
    if resource is None:
        resource = response.json()
    print(f"{action}上传已受理: {args.name} (ID {resource['id']})；需执行 verify 验收")


def verify(session, base_url, args):
    expected = args.expected_file.expanduser().resolve()
    if not expected.is_file():
        raise ConfigSetError("业务版本标记文件不存在")
    if urlsplit(args.url).scheme not in ("http", "https"):
        raise ConfigSetError("业务验收 URL 必须为 http 或 https")
    wanted = hashlib.sha256(expected.read_bytes()).hexdigest()
    resource = find_resource(session, base_url, args.name)
    if resource is None:
        raise ConfigSetError("目标租户下没有该静态资源")
    deadline = time.monotonic() + args.timeout
    expected_ids = set(args.engine_id)
    last_state = "未查询"
    # Business requests deliberately use a separate session without the controller Token.
    with requests.Session() as business:
        while time.monotonic() < deadline:
            detail = get_detail(session, base_url, resource["id"],
                                timeout=min(30, max(0.1, deadline - time.monotonic())))
            items = detail.get("sync_status_list", [])
            ready = (detail.get("upload_status") == 0 and
                     expected_ids.issubset({item.get("id") for item in items}) and
                     all(item.get("status") == 1 for item in items))
            last_state = "引擎尚未全部同步，或缺少指定引擎"
            if ready:
                try:
                    response = business.get(
                        args.url, params={"_normae_release": wanted},
                        headers={"Cache-Control": "no-cache"},
                        timeout=min(30, max(0.1, deadline - time.monotonic())),
                    )
                    if response.status_code == 200 and hashlib.sha256(
                        response.content
                    ).hexdigest() == wanted:
                        print(f"发布验收通过: {args.name}；引擎同步完成，业务版本一致")
                        return
                    last_state = "业务状态码或版本标记尚未匹配"
                except requests.RequestException:
                    last_state = "业务入口暂不可访问"
            remaining = deadline - time.monotonic()
            if remaining > 0:
                time.sleep(min(args.interval, remaining))
    raise ConfigSetError(f"发布验收超时: {last_state}；请检查同步状态和业务入口")


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    commands = parser.add_subparsers(dest="command", required=True)
    for command in ("upload", "verify"):
        sub = commands.add_parser(command)
        sub.add_argument("--name", required=True, help="目标租户内的静态资源名称")
        sub.add_argument("--tenant-id", required=True, type=int)
        if command == "upload":
            sub.add_argument("--archive", type=Path, required=True)
            sub.add_argument("--upload-timeout", type=float, default=300)
        else:
            sub.add_argument("--engine-id", type=int, action="append", required=True,
                             help="所有目标承载引擎 ID，可重复指定")
            sub.add_argument("--url", required=True, help="实际虚拟服务上的版本标记 URL")
            sub.add_argument("--expected-file", type=Path, required=True)
            sub.add_argument("--timeout", type=float, default=300)
            sub.add_argument("--interval", type=float, default=2)
    args = parser.parse_args()
    if args.tenant_id <= 0 or not args.name.strip():
        raise ConfigSetError("租户 ID 必须为正数，资源名称不能为空")
    if args.command == "upload" and args.upload_timeout <= 0:
        raise ConfigSetError("上传超时必须为正数")
    if args.command == "verify" and (
        args.timeout <= 0 or args.interval <= 0 or any(i <= 0 for i in args.engine_id)
    ):
        raise ConfigSetError("引擎 ID、超时和轮询间隔必须为正数")
    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")
    with requests.Session() as session:
        session.headers["X-NORMAE-AUTH-TOKEN"] = token
        check_tenant(session, base_url, args.tenant_id)
        (upload if args.command == "upload" else verify)(session, base_url, args)


if __name__ == "__main__":
    try:
        main()
    except (ConfigSetError, OSError, ValueError, KeyError, TypeError) 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)
