import json
import time
import urllib.parse

import pymssql
import requests
from datetime import datetime

# delivery 平台统一异步结果通知接口（与 ES 海牙 030 / 意大利 EPR 同模式）
# 第一次通知（REGISTER_INFO 注册信息）：提交注册保存结果时调用，仅 DataSource='api' 行；
# runner 可传参覆盖（受理时 bizParam.callback_url 优先）
DEFAULT_RESULT_CALLBACK_URL = "https://test-cloud.usaeu.com/prod-api/delivery/rpa/autoRegisterCallback"

# 异步结果通知投递重试：首次 + 2 次重试，第 2、3 次尝试前分别等待 2s / 6s（覆盖瞬时网络/服务抖动）
DELIVERY_NOTIFY_MAX_ATTEMPTS = 3
DELIVERY_NOTIFY_BACKOFF_SECONDS = (2, 6)

# 通知类型（回执类型）：第一次通知=注册信息，第二次通知=RPA 下号信息
RECEIPT_TYPE_REGISTER_INFO = 'REGISTER_INFO'
RECEIPT_TYPE_ISSUED_INFO = 'ISSUED_INFO'


def get_field(row, name):
    """按列名大小写不敏感取值（兼容 pymssql as_dict 键名大小写差异）"""
    if not row:
        return None
    for key, value in row.items():
        if key.lower() == name.lower():
            return value
    return None


def parse_biz_param(row):
    """解析行内 biz_param JSON；失败时回退为行内字段构造的最小 bizParam"""
    raw = get_field(row, 'biz_param')
    if isinstance(raw, str) and raw:
        try:
            parsed = json.loads(raw)
            if isinstance(parsed, dict):
                return parsed
        except json.JSONDecodeError:
            pass
    return {
        'BusinessSerialNumber': get_field(row, 'code'),
        'source_record_id': get_field(row, 'source_record_id'),
    }


def resolve_notification_url(row, delivery_callback_url):
    """通知地址优先级：受理 bizParam.callback_url > runner 传参 > 默认 delivery 地址"""
    biz = parse_biz_param(row)
    if isinstance(biz, dict) and biz.get('callback_url'):
        return biz['callback_url']
    if delivery_callback_url:
        return delivery_callback_url
    return DEFAULT_RESULT_CALLBACK_URL


def file_basename_from_url(file_url):
    """从文件 URL / 相对路径提取文件名（去查询串、锚点与 URL 编码），供异步结果通知 name 字段使用"""
    file_url = (file_url or '').strip()
    if not file_url:
        return ''
    path = urllib.parse.urlparse(file_url).path
    return urllib.parse.unquote(path.rsplit('/', 1)[-1])


def build_files(entries):
    """
    构造通知 files 数组：[{url, name, type}]。
    参数 entries: [(url, name, type), ...]；url 空串/None 过滤；name 缺省取 basename。无文件返回 None。
    """
    files = []
    for url, name, file_type in entries:
        if not isinstance(url, str) or not url.strip():
            continue
        files.append({
            'url': url.strip(),
            'name': (name or '').strip() or file_basename_from_url(url),
            'type': file_type,
        })
    return files if files else None


def send_async_result_notification(url, payload):
    """
    发送 delivery 平台统一异步结果通知
    2xx 视为送达；网络错误/超时/5xx 退避重试，最多 DELIVERY_NOTIFY_MAX_ATTEMPTS 次；
    4xx（408/429 瞬时状态除外）视为永久失败（契约错误）不重试；失败不影响主流程。

    返回:
        dict: {success, http_code, response_text, attempts, error}
    """
    headers = {'Content-Type': 'application/json; charset=utf-8'}
    request_json = json.dumps(payload, ensure_ascii=False)
    result = {
        'success': False,
        'http_code': None,
        'response_text': None,
        'attempts': 0,
        'error': None,
    }
    for attempt in range(1, DELIVERY_NOTIFY_MAX_ATTEMPTS + 1):
        result['attempts'] = attempt
        try:
            response = requests.post(
                url,
                data=request_json,
                headers=headers,
                timeout=10
            )
            result['http_code'] = response.status_code
            result['response_text'] = response.text
            if 200 <= response.status_code < 300:
                print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 异步结果通知 HTTP {response.status_code} 送达（第 {attempt} 次尝试）: {url}")
                result['success'] = True
                result['error'] = None
                return result
            if 400 <= response.status_code < 500 and response.status_code not in (408, 429):
                print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 异步结果通知 HTTP {response.status_code} 永久失败不重试: {url}")
                result['error'] = f"HTTP {response.status_code}"
                return result
            result['error'] = f"HTTP {response.status_code}"
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 异步结果通知 HTTP {response.status_code}（第 {attempt}/{DELIVERY_NOTIFY_MAX_ATTEMPTS} 次尝试）: {url}")
        except Exception as e:
            # 网络异常：清除上一次尝试的响应，保证日志只反映最后一次尝试的真实状态
            result['error'] = str(e)
            result['http_code'] = None
            result['response_text'] = None
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 异步结果通知失败（第 {attempt}/{DELIVERY_NOTIFY_MAX_ATTEMPTS} 次尝试）: {e}")
        if attempt < DELIVERY_NOTIFY_MAX_ATTEMPTS:
            wait_seconds = DELIVERY_NOTIFY_BACKOFF_SECONDS[attempt - 1]
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {wait_seconds} 秒后重试...")
            time.sleep(wait_seconds)
    return result


def save_delivery_notify_log(record_id, notify_url, payload, notify_result,
                             db_ip, db_port, db_username, db_password, database):
    """
    将 delivery 异步结果通知的请求参数与投递结果写入 uk_vat_register.delivery_notify_log（JSON）。
    列不存在（旧库未迁移）等错误仅告警，不影响主流程。
    """
    log = {
        'url': notify_url,
        'status': 'success' if notify_result['success'] else 'failed',
        'http_code': notify_result.get('http_code'),
        'attempts': notify_result.get('attempts'),
        'request': payload,
        'response': notify_result.get('response_text') or notify_result.get('error'),
    }
    conn = None
    cursor = None
    try:
        conn = pymssql.connect(
            server=db_ip,
            port=db_port,
            user=db_username,
            password=db_password,
            database=database,
            charset='utf8'
        )
        cursor = conn.cursor()
        cursor.execute(
            "UPDATE uk_vat_register SET delivery_notify_log = %s WHERE id = %s",
            (json.dumps(log, ensure_ascii=False), record_id)
        )
        conn.commit()
        print(f"异步结果通知日志已写入 uk_vat_register(id={record_id})")
    except Exception as e:
        print(f"记录异步结果通知日志失败（不影响主流程）: {e}")
    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()


def save_notify1_columns(record_id, payload, notify_result,
                         db_ip, db_port, db_username, db_password, database):
    """
    将轮1（REGISTER_INFO 注册信息）通知的请求/响应/状态/次数/时间写入 uk_vat_register 的
    Notify1Request / Notify1Response / Notify1Status / Notify1Attempts / Notify1Time。
    列不存在（旧库未迁移）等错误仅告警，不影响主流程。
    """
    conn = None
    cursor = None
    try:
        conn = pymssql.connect(
            server=db_ip,
            port=db_port,
            user=db_username,
            password=db_password,
            database=database,
            charset='utf8'
        )
        cursor = conn.cursor()
        cursor.execute(
            "UPDATE uk_vat_register SET Notify1Request = %s, Notify1Response = %s,"
            " Notify1Status = %s, Notify1Attempts = %s, Notify1Time = GETDATE() WHERE id = %s",
            (
                json.dumps(payload, ensure_ascii=False),
                notify_result.get('response_text') or notify_result.get('error'),
                'success' if notify_result['success'] else 'failed',
                notify_result.get('attempts'),
                record_id,
            )
        )
        conn.commit()
        print(f"轮1通知结果已写入 uk_vat_register Notify1* 列(id={record_id})")
    except Exception as e:
        print(f"写入 Notify1* 列失败（不影响主流程）: {e}")
    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()


def query_record_source(record_id, db_ip, db_port, db_username, db_password, database):
    """
    查询记录来源（DataSource / source_record_id / biz_param 等）
    表结构缺少新列（旧库）时返回空 dict，调用方按 source 行处理
    """
    conn = None
    cursor = None
    try:
        conn = pymssql.connect(
            server=db_ip,
            port=db_port,
            user=db_username,
            password=db_password,
            database=database,
            charset='utf8',
            as_dict=True
        )
        cursor = conn.cursor()
        cursor.execute(
            "SELECT DataSource, biz_param, code, source_record_id, customer_email "
            "FROM uk_vat_register WHERE id = %s",
            (record_id,)
        )
        row = cursor.fetchone()
        return row if row else {}
    except pymssql.Error as e:
        print(f"查询记录来源失败（按 source 行处理）: {e}")
        return {}
    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()


def send_callback_request(
    callback_url,
    callback_token,
    vat_business_record_id,
    mtd_account,
    mtd_password,
    mtd_key,
    reg_receipt,
    reg_receipt_number,
    other_file,
    record_id,
    db_ip,
    db_port,
    db_username,
    db_password,
    database,
    delivery_callback_url=None
):
    """
    第一步：注册完成保存结果（写库 + 按来源分流）。
    source 行走老 open-token 回调（callback_url → succeeded 字段判定注册结果 → 写库），
    回调响应 HTTP 200 且 succeeded=true 置注册状态 2（成功），否则置 3（失败，后续由 rpa_step1_get 重试）；
    api 行注册必成功（到达本步骤即已向税局提交成功，VRS 回执编号已拿到），恒写 2（成功），
    写库后发第一次异步结果通知 REGISTER_INFO（delivery 接口），通知请求/响应写入 Notify1* 列（轮1）
    与 delivery_notify_log。source 与 api 是两套完全不同的处理流程，不存在回退或兼容。

    参数:
        callback_url (str): 老 open-token 回调 URL（仅 source 行使用）
        callback_token (str): 认证token（仅 source 行使用）
        vat_business_record_id (str): VAT业务记录ID（仅 source 行使用，随回调请求提交）
        record_id (int): 数据库记录ID
        db_ip (str): 数据库IP地址
        db_port (int): 数据库端口
        db_username (str): 数据库用户名
        db_password (str): 数据库密码
        database (str): 数据库名称
        mtd_account (str, optional): MTD账号
        mtd_password (str, optional): MTD密码
        mtd_key (str, optional): MTD秘钥
        reg_receipt (str or list, optional): 注册回执文件（source 行完整 URL / api 行 OSS 相对路径，字符串或列表）
        reg_receipt_number (str, optional): 注册回执编号（VRS 编码，即通知里的 vrsReceiptCode）
        other_file (str or list, optional): 注册确认文件（source 行完整 URL / api 行 OSS 相对路径，字符串或列表）
        delivery_callback_url (str, optional): delivery 统一异步结果通知地址（仅 api 行使用），
            缺省用 DEFAULT_RESULT_CALLBACK_URL

    返回:
        dict: {'success': bool, 'http_code': int, 'response_data': dict,
               'register_status': int, 'error': str}（api 行 http_code / response_data 为 None）
    """
    if isinstance(reg_receipt, str):
        reg_receipt_array = [reg_receipt] if reg_receipt else []
    elif isinstance(reg_receipt, list):
        reg_receipt_array = reg_receipt
    else:
        reg_receipt_array = []

    if isinstance(other_file, str):
        other_file_array = [other_file] if other_file else []
    elif isinstance(other_file, list):
        other_file_array = other_file
    else:
        other_file_array = []

    # 记录来源：source 行走老 open-token 回调（callback_url → succeeded 判定 → 写库）；
    # api 行走 delivery 统一异步结果通知（两轮），两套系统互不兼容、无回退
    row_source = query_record_source(record_id, db_ip, db_port, db_username, db_password, database)
    data_source = str(get_field(row_source, 'DataSource') or 'source').lower()

    result = {
        'success': False,
        'http_code': None,
        'response_data': None,
        'register_status': 3,  # 默认为失败
        'error': None,
    }

    # ---------- source 行：老 open-token 回调 → succeeded 判定 → 写库，不调 delivery ----------
    if data_source != 'api':
        # 构建请求数据（完整原始请求 JSON，与拆分前一致，落 submit_backup_data）
        data = {
            "vatBusinessRecordId": vat_business_record_id,
            "mtdInfo": {
                "mtdAccount": mtd_account,
                "mtdPassword": mtd_password,
                "mtdKey": mtd_key
            },
            "regReceipt": reg_receipt_array,
            "regReceiptNumber": reg_receipt_number,
            "otherFile": other_file_array
        }

        # 转换为JSON字符串
        json_data = json.dumps(data, ensure_ascii=False)

        # 设置请求头
        headers = {
            'open-token': callback_token,
            'Content-Type': 'application/json-patch+json',
            'Content-Length': str(len(json_data.encode('utf-8')))
        }

        try:
            # 发送POST请求
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 发送请求到: {callback_url}")
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 请求数据: {json_data}")

            response = requests.post(
                callback_url,
                data=json_data,
                headers=headers,
                timeout=30
            )

            # 获取响应信息
            http_code = response.status_code
            result['http_code'] = http_code

            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] HTTP状态码: {http_code}")

            # 解析响应JSON
            try:
                response_json = response.json()
                result['response_data'] = response_json
                print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 响应: {json.dumps(response_json, ensure_ascii=False)}")

                # 判断注册状态
                # 成功条件: HTTP状态码为200 且 succeeded字段为true
                if http_code == 200 and response_json.get('succeeded') == True:
                    result['register_status'] = 2  # 成功
                    result['success'] = True
                    print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 注册成功")
                else:
                    result['register_status'] = 3  # 失败
                    result['error'] = response_json.get('message', '注册失败')
                    print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 注册失败: {result['error']}")

            except json.JSONDecodeError as e:
                result['error'] = f"响应JSON解析失败: {str(e)}"
                print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 错误: {result['error']}")
                print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 原始响应: {response.text}")
                return result

        except requests.exceptions.Timeout:
            result['error'] = "请求超时"
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 错误: {result['error']}")
            return result
        except requests.exceptions.RequestException as e:
            result['error'] = f"请求异常: {str(e)}"
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 错误: {result['error']}")
            return result
        except Exception as e:
            result['error'] = f"未知错误: {str(e)}"
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 错误: {result['error']}")
            return result

        # 更新数据库
        try:
            update_database(
                db_ip,
                db_port,
                db_username,
                db_password,
                database,
                record_id,
                json_data,
                result['register_status'],
                mtd_account,
                mtd_password,
                mtd_key
            )
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 数据库更新成功, 记录ID: {record_id}, 状态: {result['register_status']}")
        except Exception as e:
            result['error'] = f"数据库更新失败: {str(e)}"
            result['success'] = False
            print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 错误: {result['error']}")
            return result

        return result

    # ---------- api 行：注册必成功（到达本步骤必有 VRS 回执编号），写库后发 delivery 通知 ----------
    try:
        update_database(
            db_ip,
            db_port,
            db_username,
            db_password,
            database,
            record_id,
            json.dumps({
                'mtd_account': mtd_account,
                'reg_receipt': reg_receipt_array,
                'reg_receipt_number': reg_receipt_number,
                'other_file': other_file_array,
            }, ensure_ascii=False),
            2,
            mtd_account,
            mtd_password,
            mtd_key
        )
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 数据库更新成功, 记录ID: {record_id}, 状态: 2")
        result['register_status'] = 2
        result['success'] = True
        result['error'] = None
    except Exception as e:
        result['error'] = f"数据库更新失败: {str(e)}"
        result['success'] = False
        print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] 错误: {result['error']}")
        return result

    # ---------- api 行：第一次异步结果通知（REGISTER_INFO 注册信息；注册必成功，只发 200 载荷） ----------
    notify_url = resolve_notification_url(row_source, delivery_callback_url)
    biz_param = parse_biz_param(row_source)
    payload = {
        'code': 200,
        'msg': 'success',
        'ProcessMode': 'async',
        'data': {
            'receiptType': RECEIPT_TYPE_REGISTER_INFO,
            'vrsReceiptCode': reg_receipt_number or '',
            'mtdAccount': mtd_account or '',
            'mtdPassword': mtd_password or '',
            'mtdSecretKey': mtd_key or '',
            'mtdRegisterEmail': get_field(row_source, 'customer_email') or '',
            'files': build_files([
                (url, '', 'REGISTRATION_RECEIPT_FILE') for url in reg_receipt_array
            ] + [
                (url, '', 'REGISTRATION_CONFIRMATION_FILE') for url in other_file_array
            ]),
        },
        'bizParam': biz_param,
    }
    notify_result = send_async_result_notification(notify_url, payload)
    save_notify1_columns(record_id, payload, notify_result,
                         db_ip, db_port, db_username, db_password, database)
    save_delivery_notify_log(record_id, notify_url, payload, notify_result,
                             db_ip, db_port, db_username, db_password, database)
    return result


def update_database(db_ip, db_port, db_username, db_password, database,
                    record_id, submit_data, register_status,
                    mtd_account=None, mtd_password=None, mtd_key=None):
    """
    更新数据库记录
    """
    conn = None
    cursor = None

    try:
        conn = pymssql.connect(
            server=db_ip,
            port=db_port,
            user=db_username,
            password=db_password,
            database=database,
            charset='utf8'
        )
        cursor = conn.cursor()
        sql = """
            UPDATE uk_vat_register
            SET submit_backup_data = %s,
                registration_status = %s,
                mtd_account = %s,
                mtd_password = %s,
                mtd_key = %s,
                updated_date = GETDATE()
            WHERE id = %s
        """
        cursor.execute(sql, (
            submit_data,
            register_status,
            mtd_account,
            mtd_password,
            mtd_key,
            record_id
        ))
        conn.commit()

    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()
