"""主案、子方案和共享附件只读 HTTP 接口。"""

from __future__ import annotations

import json
import logging

from flask import current_app, request

from dms.api.v1.blueprint import api_v1
from dms.common.errors import (
    BatchManifestMismatchError,
    DmsError,
    InvalidArgumentError,
    PayloadTooLargeError,
)
from dms.common.response import success_response
from dms.extensions import db
from dms.security.decorators import admin_required, bearer_auth_required
from dms.services.attachment_query_service import (
    get_attachment,
    list_attachment_main_plans,
    list_attachments,
    list_main_plan_attachments,
)
from dms.services.attachment_binding_service import (
    bind_attachments,
    unbind_attachment,
)
from dms.services.document_query_service import (
    get_document,
    list_documents,
    list_main_plan_tags,
    list_sub_plans,
    parse_string_id,
)
from dms.services.document_mutation_service import (
    create_attachment,
    create_document,
    delete_attachment,
    delete_document,
    update_attachment,
    update_document,
)
from dms.services.file_read_service import download_document, preview_document
from dms.services.permission_service import get_permissions, save_permissions

logger = logging.getLogger(__name__)


def _metadata() -> dict:
    if set(request.files) != {"file"} or set(request.form) != {"metadata"}:
        raise InvalidArgumentError("请求必须且只能包含file和metadata")
    try:
        payload = json.loads(request.form["metadata"])
    except (json.JSONDecodeError, TypeError) as exc:
        raise InvalidArgumentError("metadata必须是JSON对象字符串") from exc
    if not isinstance(payload, dict):
        raise InvalidArgumentError("metadata必须是JSON对象字符串")
    return payload


def _json_body() -> dict:
    if not request.is_json:
        raise InvalidArgumentError("请求体必须是JSON对象")
    payload = request.get_json(silent=True)
    if not isinstance(payload, dict):
        raise InvalidArgumentError("请求体必须是JSON对象")
    return payload


def _row_version() -> int:
    value = request.args.get("rowVersion")
    if value is None or not value.isdecimal():
        raise InvalidArgumentError("rowVersion必须是非负整数")
    return int(value)


def _main_plan_row_version() -> int:
    if set(request.args) != {"mainPlanRowVersion"}:
        raise InvalidArgumentError(
            "查询参数必须且只能包含mainPlanRowVersion"
        )
    value = request.args.get("mainPlanRowVersion")
    if value is None or not value.isdecimal():
        raise InvalidArgumentError("mainPlanRowVersion必须是非负整数")
    return int(value)


def _batch(*, attachment: bool):
    if set(request.files) != {"files"} or set(request.form) != {"items"}:
        raise InvalidArgumentError("批量请求必须且只能包含files和items")
    files = request.files.getlist("files")
    try:
        items = json.loads(request.form["items"])
    except (json.JSONDecodeError, TypeError) as exc:
        raise InvalidArgumentError("items必须是JSON数组字符串") from exc
    if not isinstance(items, list):
        raise InvalidArgumentError("items必须是JSON数组字符串")
    if len(files) != len(items):
        raise BatchManifestMismatchError()
    if not files:
        raise InvalidArgumentError("批量文件不能为空")
    if len(files) > current_app.config["DMS_BATCH_MAX_FILES"]:
        raise PayloadTooLargeError("批量文件数量超过允许上限")
    results = []
    success_count = 0
    for index, (file, payload) in enumerate(zip(files, items, strict=True)):
        original_name = file.filename or ""
        try:
            document = (
                create_attachment(file, payload, batch=True)
                if attachment
                else create_document(file, payload, batch=True)
            )
            results.append(
                {
                    "index": index,
                    "originalFileName": original_name,
                    "success": True,
                    "document": document,
                }
            )
            success_count += 1
        except DmsError as exc:
            db.session.rollback()
            results.append(
                {
                    "index": index,
                    "originalFileName": original_name,
                    "success": False,
                    "errorCode": exc.code,
                    "errorMessage": exc.message,
                }
            )
        except Exception:
            db.session.rollback()
            logger.exception("批量导入单项发生未处理异常：index=%s", index)
            results.append(
                {
                    "index": index,
                    "originalFileName": original_name,
                    "success": False,
                    "errorCode": "INTERNAL_ERROR",
                    "errorMessage": "服务器内部错误",
                }
            )
    return {
        "total": len(files),
        "successCount": success_count,
        "failureCount": len(files) - success_count,
        "items": results,
    }


@api_v1.get("/documents")
@bearer_auth_required
def documents_list_route():
    return success_response(list_documents(request.args))


@api_v1.get("/documents/tags")
@bearer_auth_required
def documents_tags_route():
    """聚合当前用户可见的主案标签，供前端左侧标签筛选条使用。"""
    return success_response(list_main_plan_tags())


@api_v1.post("/documents")
@bearer_auth_required
@admin_required
def create_document_route():
    payload = _metadata()
    return success_response(
        create_document(request.files["file"], payload),
        status=201,
    )


@api_v1.post("/documents/batch-import")
@bearer_auth_required
@admin_required
def batch_import_documents_route():
    return success_response(_batch(attachment=False))


@api_v1.get("/documents/<id>")
@bearer_auth_required
def document_detail_route(id: str):
    return success_response(get_document(parse_string_id(id)))


@api_v1.put("/documents/<id>")
@bearer_auth_required
@admin_required
def update_document_route(id: str):
    return success_response(
        update_document(parse_string_id(id), _json_body())
    )


@api_v1.delete("/documents/<id>")
@bearer_auth_required
@admin_required
def delete_document_route(id: str):
    return success_response(
        delete_document(parse_string_id(id), _row_version())
    )


@api_v1.get("/documents/<id>/preview")
@bearer_auth_required
def preview_document_route(id: str):
    return preview_document(parse_string_id(id))


@api_v1.get("/documents/<id>/download")
@bearer_auth_required
def download_document_route(id: str):
    return download_document(parse_string_id(id))


@api_v1.get("/main-plans/<id>/sub-plans")
@bearer_auth_required
def sub_plans_list_route(id: str):
    return success_response(list_sub_plans(parse_string_id(id), request.args))


@api_v1.get("/attachments")
@bearer_auth_required
@admin_required
def attachments_list_route():
    return success_response(list_attachments(request.args))


@api_v1.post("/attachments")
@bearer_auth_required
@admin_required
def create_attachment_route():
    payload = _metadata()
    return success_response(
        create_attachment(request.files["file"], payload),
        status=201,
    )


@api_v1.post("/attachments/batch-import")
@bearer_auth_required
@admin_required
def batch_import_attachments_route():
    return success_response(_batch(attachment=True))


@api_v1.get("/attachments/<id>")
@bearer_auth_required
def attachment_detail_route(id: str):
    return success_response(get_attachment(parse_string_id(id)))


@api_v1.put("/attachments/<id>")
@bearer_auth_required
@admin_required
def update_attachment_route(id: str):
    return success_response(
        update_attachment(parse_string_id(id), _json_body())
    )


@api_v1.delete("/attachments/<id>")
@bearer_auth_required
@admin_required
def delete_attachment_route(id: str):
    return success_response(
        delete_attachment(parse_string_id(id), _row_version())
    )


@api_v1.get("/attachments/<id>/main-plans")
@bearer_auth_required
def attachment_main_plans_route(id: str):
    return success_response(
        list_attachment_main_plans(parse_string_id(id))
    )


@api_v1.get("/main-plans/<id>/attachments")
@bearer_auth_required
def main_plan_attachments_route(id: str):
    return success_response(
        list_main_plan_attachments(parse_string_id(id), request.args)
    )


@api_v1.post("/main-plans/<id>/attachments/bind")
@bearer_auth_required
@admin_required
def bind_main_plan_attachments_route(id: str):
    return success_response(
        bind_attachments(parse_string_id(id), _json_body())
    )


@api_v1.delete("/main-plans/<id>/attachments/<attachmentId>")
@bearer_auth_required
@admin_required
def unbind_main_plan_attachment_route(id: str, attachmentId: str):
    return success_response(
        unbind_attachment(
            parse_string_id(id),
            parse_string_id(attachmentId, field="attachmentId"),
            _main_plan_row_version(),
        )
    )


@api_v1.get("/documents/<id>/permissions")
@bearer_auth_required
@admin_required
def document_permissions_route(id: str):
    return success_response(get_permissions(parse_string_id(id)))


@api_v1.put("/documents/<id>/permissions")
@bearer_auth_required
@admin_required
def save_document_permissions_route(id: str):
    return success_response(
        save_permissions(parse_string_id(id), _json_body())
    )
