import datetime
import logging
import os
import socket
import subprocess

import requests

from flask import Flask, jsonify, request, current_app, send_from_directory, Response
from flask_cors import CORS
from requests_toolbelt import MultipartEncoder

from config import file_type_map, TARGET_URL
from tools.file_manager import get_file_md5
from tools.logger_handle import logger

socket.getfqdn = lambda name=None: 'localhost'

app = Flask(__name__, static_folder='../static')

CORS(app, resources=r'/*')


class InterceptHandler(logging.Handler):
    def emit(self, record):
        # 将标准日志记录转为 loguru
        try:
            level = logger.level(record.levelname).name
        except ValueError:
            level = record.levelno
        logger.opt(depth=6, exception=record.exc_info).log(level, record.getMessage())


logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO)
for name in ("flask", "werkzeug", "watchdog"):
    logging.getLogger(name).handlers = [InterceptHandler()]
    logging.getLogger(name).propagate = False


@app.route('/')
def index():
    return send_from_directory(app.static_folder, 'index.html')


# 代理静态文件（如 css, js, images 等）
@app.route('/<path:path>')
def static_proxy(path):
    return send_from_directory(app.static_folder, path)


@logger.catch()
@app.route('/pyapi/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
def proxy(path):
    url = f'{TARGET_URL}/{path}'
    logger.info(f'接收到请求{url}')
    raw_body = request.get_data(cache=True)
    # 获取 headers（排除 Host 避免冲突）
    headers = {key: value for key, value in request.headers if key.lower() != 'host'}
    headers[
        'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36'

    try:
        if request.files or (request.content_type and 'multipart/form-data' in request.content_type.lower()):
            # 构造 Multipart 表单
            fields = {}

            # 添加普通字段
            if request.files:
                for key, value in request.form.items():
                    fields[key] = value

            # 添加文件字段
                for key, file in request.files.items():
                    fields[key] = (file.filename, file.stream, file.mimetype)

                form_data = MultipartEncoder(fields=fields)
                headers['Content-Type'] = form_data.content_type
            else:
                form_data = raw_body

            resp = requests.request(
                method=request.method,
                url=url,
                headers=headers,
                params=request.args,
                data=form_data,
                cookies=request.cookies,
                allow_redirects=False
            )
        else:
            # JSON 或普通表单
            json_data = None
            form_data = None
            if request.content_type and 'application/json' in request.content_type.lower():
                json_data = request.get_json(silent=True)
            else:
                form_data = request.form.to_dict()

            resp = requests.request(
                method=request.method,
                url=url,
                headers=headers,
                params=request.args,
                data=form_data,
                json=json_data,
                cookies=request.cookies,
                allow_redirects=False
            )

    except Exception as e:
        logger.error(e)
        return jsonify({'code': -1})

    # 构建 Flask 的响应
    excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
    response_headers = [(k, v) for k, v in resp.raw.headers.items() if k.lower() not in excluded_headers]

    return Response(resp.content, resp.status_code, response_headers)


@logger.catch()
def open_file_by_wps(file_path):
    ext = file_path.split(".")[-1]
    if ext not in file_type_map:
        return jsonify({'code': 3001, 'msg': '不支持该类型的文件'})

    if not os.path.exists(file_path):
        return jsonify({'code': 3006, 'msg': '文件不存在'})

    subprocess.run([file_type_map[ext], file_path])

    return jsonify({'code': 1000, 'msg': '操作完成'})


@logger.catch()
@app.route('/download_and_open_file', methods=['POST'])
def download_and_open_with_wps():
    '''
    1000: 操作完成
    2001: 检测到文档正在被编辑，无法下载
    2002: 检测到远程文件已经更新本地文件也发生改动，终止操作
    3001: 不支持该类型的文件
    3002: 下载文件失败
    :return:
    '''
    file_id = request.get_json()['file_id']
    is_template = request.get_json()['is_template']
    # 判断是否为exe模式执行

    if is_template:
        file_info = app.config['serve_client'].get_template_file_info(file_id)
        time_str = datetime.datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
        local_path = os.path.join(current_app.config['work_path'],
                                  f'{file_info["name"]}({time_str}).{file_info["nameSuffix"]}')

        if app.config['serve_client'].download_template(file_info['fileLink'], local_path):
            return open_file_by_wps(local_path)
        else:
            return jsonify({'code': 3006, 'msg': '文件不存在'})

    file_info = app.config['serve_client'].get_file_info(file_id)
    if not file_info:
        return jsonify({'code': 3006, 'msg': '文件不存在'})

    local_file = os.path.join(current_app.config['work_path'], file_info['filePath'].replace('/', '_'))

    # 判断本地是否存在已下载的同名文件
    if not os.path.exists(local_file) or not os.path.exists(local_file + '.metadata.json'):
        app.config['serve_client'].download_file(file_info,
                                                 current_app.config['work_path'])

        return open_file_by_wps(local_file)

    # 判断本地文件和线上文件是否一致
    local_file_metadata = current_app.config['serve_client'].load_metadata(local_file)
    if local_file_metadata['update_time'] == file_info['updateTime']:
        return open_file_by_wps(local_file)

    if local_file_metadata['md5'] == app.config['serve_client'].get_file_md5(local_file):
        app.config['serve_client'].download_file(file_info, current_app.config['work_path'])

        return open_file_by_wps(local_file)

    return jsonify({'code': 2002, 'msg': '检测到远程文件已经更新本地文件也发生改动，终止操作'})


@logger.catch()
@app.route('/upload_local_file', methods=['POST'])
def upload_local_file():
    file_name = request.get_json()['file_name']

    metadata = app.config['serve_client'].load_metadata(os.path.join(current_app.config['work_path'], file_name))
    code = app.config['serve_client'].upload_file(os.path.join(current_app.config['work_path'], file_name))
    if not code:
        return jsonify({'code': 3003, 'msg': '操作失败，上传文件失败'})

    try:
        os.remove(os.path.join(current_app.config['work_path'], file_name))
        os.remove(os.path.join(current_app.config['work_path'], file_name + '.metadata'))

        file_info = app.config['serve_client'].get_file_info(metadata['file_id'])
        if not file_info:
            return jsonify({'code': 3006, 'msg': '文件不存在'})

        app.config['serve_client'].download_file(file_info, current_app.config['work_path'])
    except Exception as e:
        logger.exception(e)
        return jsonify({'code': 1000, 'msg': '文件上传成功，本地文件清除失败'})

    return jsonify({'code': 1000, 'msg': '操作成功'})


@logger.catch()
@app.route('/update_local_file', methods=['POST'])
def update_local_file():
    file_name = request.get_json()['file_name']

    try:
        metadata = current_app.config['serve_client'].load_metadata(os.path.join(current_app.config['work_path'], file_name))

        os.remove(os.path.join(current_app.config['work_path'], file_name))
        os.remove(os.path.join(current_app.config['work_path'], file_name + '.metadata'))
    except Exception as e:
        logger.exception(e)
        return jsonify({'code': 3005, 'msg': '操作失败，无权限操作本地文件'})

    try:
        file_info = app.config['serve_client'].get_file_info(metadata['file_id'])
        if not app.config['serve_client'].download_file(file_info, current_app.config['work_path']):
            return jsonify({'code': 3005, 'msg': '文件下载失败'})
    except Exception as e:
        logger.exception(e)
        return jsonify({'code': 3005, 'msg': '文件下载失败'})

    return jsonify({'code': 1000, 'msg': '操作成功'})


@logger.catch()
@app.route('/file_state_list', methods=['GET'])
def file_state_list():
    '''
    state : 0 正常， 1: 云端有更新 2: 本地有变动且无冲突可以上传 3: 本地文件和云端文件有冲突
    :return:
    '''
    file_info_list = []
    for file_name in os.listdir(current_app.config['work_path']):
        suffer = file_name.split('.')[-1]
        if suffer not in file_type_map:
            continue

        if not os.path.exists(os.path.join(current_app.config['work_path'], file_name + '.metadata')):
            continue

        metadata = current_app.config['serve_client'].load_metadata(os.path.join(current_app.config['work_path'], file_name))
        file_info = {
            'file_name': file_name,
            'show_name': metadata['file_name'],
            'cloud_update_time': metadata['update_time'],
            'source_md5': metadata['md5'],
            'state': 0
        }
        cloud_file_info = current_app.config['serve_client'].get_file_info(metadata['file_id'])
        if cloud_file_info == 2:
            file_info['state'] = 4
            file_info_list.append(file_info)
            continue

        if not cloud_file_info:
            file_info_list.append(file_info)
            continue

        if cloud_file_info['updateTime'] != metadata['update_time']:
            file_info['state'] = 1

        try:
            file_md5 = get_file_md5(os.path.join(current_app.config['work_path'], file_name))
            if file_md5 != metadata['md5']:
                if file_info['state'] == 1:
                    file_info['state'] = 3
                else:
                    file_info['state'] = 2
        except Exception as e:
            logger.exception(e)
            pass

        file_info_list.append(file_info)

    return jsonify({'code': 1000, 'msg': '操作成功', 'data': file_info_list})


@logger.catch()
@app.route('/remove_file', methods=['POST'])
def remove_file():
    file_name = request.get_json()['file_name']
    file_path = os.path.join(current_app.config['work_path'], file_name)
    if not os.path.exists(file_path):
        return jsonify({'code': '3006', 'msg': '文件不存在'})

    try:
        os.remove(file_path)
        os.remove(f'{file_path}.metadata')
    except Exception as e:
        logger.exception(e)
        return jsonify({'code': 3005, 'msg': '操作失败，无权限操作本地文件'})

    return jsonify({'code': 1000, 'msg': '操作成功'})


@logger.catch()
@app.route('/start_wps_server', methods=['POST'])
def start_wps_server():
    url = f"ksowpscloudsvr://start=RelayHttpServer&serverId=aef5ac0d-d5a3-49ee-b02f-c31eeb063f9b"
    os.startfile(url)
    return jsonify({'code': 1000, 'msg': '操作成功'})


@logger.catch()
@app.route('/open_file', methods=['POST'])
def open_file():
    file_name = request.get_json()['file_name']
    file_path = os.path.join(current_app.config['work_path'], file_name)

    if not os.path.exists(file_path):
        return jsonify({'code': '3006', 'msg': '文件不存在'})

    return open_file_by_wps(file_path)


@logger.catch()
@app.route('/create_cloud_file', methods=['POST'])
def create_cloud_file():
    params = request.get_json()
    flag = app.config['serve_client'].create_cloud_file(local_file=params['local_file'], folder_id=params['folder_id'])
    return jsonify({'code': 1000, 'msg': '文件上传成功' if flag else '网络错误，上传失败。'})


@logger.catch()
@app.route('/get_folder_tree', methods=['POST'])
def get_base_path():
    res = app.config['serve_client'].get_folder_tree()
    return jsonify(res)


@logger.catch()
@app.route('/get_token', methods=['POST'])
def get_token():
    return jsonify(app.config['serve_client'].login_reply)


@logger.catch()
@app.route('/refresh_token', methods=['POST'])
def refresh_token():
    app.config['serve_client'].login()
    return jsonify(app.config['serve_client'].login_reply)


@logger.catch()
@app.route('/download_cloud_file', methods=['POST'])
def download_cloud_file():
    '''
    1000: 操作完成
    2001: 检测到文档正在被编辑，无法下载
    2002: 检测到远程文件已经更新本地文件也发生改动，终止操作
    3001: 不支持该类型的文件
    3002: 下载文件失败
    :return:
    '''
    file_id = request.get_json()['file_id']
    is_template = request.get_json()['is_template']
    # 判断是否为exe模式执行

    if is_template:
        file_info = app.config['serve_client'].get_template_file_info(file_id)
        time_str = datetime.datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
        local_path = os.path.join(current_app.config['work_path'],
                                  f'{file_info["name"]}({time_str}).{file_info["nameSuffix"]}')

        if app.config['serve_client'].download_template(file_info['fileLink'], local_path):
            return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_path})
        else:
            return jsonify({'code': 3006, 'msg': '文件不存在'})

    file_info = app.config['serve_client'].get_file_info(file_id)
    if not file_info:
        return jsonify({'code': 3006, 'msg': '文件不存在'})

    local_file = os.path.join(current_app.config['work_path'], file_info['filePath'].replace('/', '_'))

    # 判断本地是否存在已下载的同名文件
    if not os.path.exists(local_file) or not os.path.exists(local_file + '.metadata.json'):
        try:
            app.config['serve_client'].download_file(file_info,
                                                     current_app.config['work_path'])
        except Exception as e:
            logger.exception(e)
            return jsonify({'code': 3002, 'msg': '下载文件失败'})

        return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_file})

    # 判断本地文件和线上文件是否一致
    local_file_metadata = current_app.config['serve_client'].load_metadata(local_file)
    if local_file_metadata['update_time'] == file_info['updateTime']:
        return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_file})

    if local_file_metadata['md5'] == app.config['serve_client'].get_file_md5(local_file):
        app.config['serve_client'].download_file(file_info, current_app.config['work_path'])

        return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_file})

    return jsonify({'code': 2002, 'msg': '检测到远程文件已经更新本地文件也发生改动，终止操作'})


def start_flask(serve_client, work_path):
    app.config['serve_client'] = serve_client
    app.config['work_path'] = work_path

    app.run(host='127.0.0.1', port=5855)
