# client.py
from flask import Flask, request, Response, jsonify
import subprocess, requests, re
import win32gui, win32con, win32process
import time

app = Flask(__name__)

# 서버 HTML을 가져올 서버 URL을 정의합니다.
SERVER_URL = 'http://g.geomedical.kr/'

# subprocess 실행 시 콘솔 창이 뜨지 않도록 하는 플래그
_NO_WINDOW = subprocess.CREATE_NO_WINDOW

# === 입력값 검증 ===
VALID_DRIVES = {'G', 'Q', 'R'}

def validate_drive(drive):
    """드라이브 문자 검증 — G, Q, R만 허용"""
    if not drive or drive.upper() not in VALID_DRIVES:
        return None, "유효하지 않은 드라이브입니다. (G, Q, R만 허용)"
    return drive.upper(), None

def validate_credentials(username, password):
    """자격 증명 기본 검증"""
    if not username or not password:
        return "누락된 정보가 있습니다."
    if '\x00' in username or '\x00' in password:
        return "허용되지 않는 문자가 포함되어 있습니다."
    if '\n' in username or '\r' in username:
        return "아이디에 허용되지 않는 문자가 포함되어 있습니다."
    if len(username) > 100 or len(password) > 200:
        return "입력값이 너무 깁니다."
    return None

def get_network_path(drive, external_access):
    """드라이브와 접속 방식에 따른 네트워크 경로 반환"""
    if external_access:
        paths = {
            'G': r'https://geomedical.kr:5006/GDRIVE',
            'Q': r'https://geomedical.kr:5006/home',
            'R': r'https://geomedical.kr:5006/GDRIVE',
        }
    else:
        paths = {
            'G': r'\\192.168.2.4\GDRIVE',
            'Q': r'\\192.168.2.4\home',
            'R': r'\\192.168.2.2\gdrive',
        }
    return paths.get(drive, paths['G'])

# --- 루트: 서버 HTML을 실시간으로 가져와 반환 (동기화) ---
@app.route('/', methods=['GET'])
def agent_index():
    try:
        r = requests.get(SERVER_URL, timeout=5)
        return Response(r.text, status=r.status_code, mimetype='text/html')
    except Exception as e:
        return f"서버({SERVER_URL})에 접속 실패: {e}", 500

# --- 계정 생성 요청 처리 (서버로 프록시 — 토큰은 서버에서 관리) ---
@app.route('/create_account_request', methods=['POST'])
def create_account_request():
    try:
        data = request.get_json()
        if not data:
            return jsonify({"success": False, "message": "유효하지 않은 요청 데이터"}), 400

        fullName = data.get('fullName')
        if not fullName:
            return jsonify({"success": False, "message": "이름이 누락되었습니다."}), 400

        # 서버로 전달 (토큰은 서버에서 처리)
        server_url = SERVER_URL.rstrip('/') + '/api/create_account_request'
        resp = requests.post(server_url, json={"fullName": fullName}, timeout=10)
        return Response(resp.text, status=resp.status_code, mimetype='application/json')
    except requests.exceptions.RequestException as e:
        return jsonify({"success": False, "message": f"서버 통신 오류: {str(e)}"}), 500
    except Exception as e:
        return jsonify({"success": False, "message": f"서버 오류: {str(e)}"}), 500

# --- 네트워크 드라이브 연결 (로컬 실행) ---
@app.route('/connect', methods=['POST'])
def connect_drive():
    data = request.get_json()
    username = data.get('username', '')
    password = data.get('password', '')
    drive_raw = data.get('drive', '')
    external_access = data.get('externalAccess', False)

    # 입력값 검증
    cred_error = validate_credentials(username, password)
    if cred_error:
        return cred_error, 400

    drive, drive_error = validate_drive(drive_raw)
    if drive_error:
        return drive_error, 400

    network_path = get_network_path(drive, external_access)

    # 1. 먼저 기존 연결 상태 확인
    check_result = subprocess.run(['net', 'use'], capture_output=True, text=True,
                                   creationflags=_NO_WINDOW)

    # 2. 선택한 드라이브에 대해 이미 연결이 있으면 먼저 해제
    if f"{drive}:" in check_result.stdout:
        subprocess.run(['net', 'use', f'{drive}:', '/delete', '/yes'],
                       capture_output=True, text=True, creationflags=_NO_WINDOW)

    # 3. 연결
    cmd = ['net', 'use', f'{drive}:', network_path,
           f'/user:{username}', password, '/persistent:no']

    try:
        result = subprocess.run(cmd, capture_output=True, text=True,
                                creationflags=_NO_WINDOW)

        # 연결 성공한 경우 - 바로 반환
        if result.returncode == 0:
            open_explorer_maximized(f"{drive}:")
            connection_type = "WebDAV(외부)" if external_access else "내부 네트워크"
            return f"[{drive}:] 드라이브 연결 성공! ({connection_type})\n{result.stdout}"
        else:
            # 연결 실패 - 오류 분석
            error_msg = result.stderr.strip() if result.stderr else "알 수 없는 오류"
            error_code = re.search(r'시스템 오류 (\d+)', error_msg)

            if error_code:
                error_num = error_code.group(1)

                # 오류 1219, 85, 67, 53 일 경우 처리
                if error_num in ["1219", "85", "67", "53"]:
                    # 서버 경로에 대한 모든 연결 확인 (192.168.2.x로 시작하는)
                    server_connections = re.findall(
                        r'\\\\192\.168\.2\.\d+\\[^\s]+', check_result.stdout)

                    # 각 서버 연결에 대한 처리
                    for connection in server_connections:
                        # 연결된 드라이브 문자 확인 (있는 경우)
                        drive_matches = re.findall(
                            fr'([A-Z]:)\s+{re.escape(connection)}',
                            check_result.stdout)
                        for drive_letter in drive_matches:
                            subprocess.run(
                                ['net', 'use', drive_letter, '/delete', '/yes'],
                                capture_output=True, text=True,
                                creationflags=_NO_WINDOW)

                        # 네트워크 경로 자체에 대한 연결도 해제
                        subprocess.run(
                            ['net', 'use', connection, '/delete', '/yes'],
                            capture_output=True, text=True,
                            creationflags=_NO_WINDOW)

                    # 추가로 모든 연결 해제 (더 강력한 방법)
                    subprocess.run(['net', 'use', '*', '/delete', '/yes'],
                                   capture_output=True, text=True,
                                   creationflags=_NO_WINDOW)

                    # 다시 연결 시도
                    retry_result = subprocess.run(cmd, capture_output=True, text=True,
                                                   creationflags=_NO_WINDOW)
                    if retry_result.returncode == 0:
                        open_explorer_maximized(f"{drive}:")
                        connection_type = "WebDAV(외부)" if external_access else "내부 네트워크"
                        return f"[{drive}:] 드라이브 연결 성공 (재시도 후)! ({connection_type})\n{retry_result.stdout}"
                    else:
                        retry_error = retry_result.stderr.strip() if retry_result.stderr else "알 수 없는 오류"
                        error_message = f"[{drive}:] 드라이브 연결 실패 (재시도 후)!\n에러: {retry_error}"
                        if external_access:
                            error_message += "\n\n외부 접속(WebDAV) 연결 실패 가능성:\n1. 인터넷 연결을 확인하세요.\n2. VPN 연결이 필요할 수 있습니다.\n3. 외부 접속이 허용된 계정인지 확인하세요."
                        return error_message, 400
                else:
                    # 다른 오류 코드의 경우 상세 정보 반환
                    error_message = f"[{drive}:] 드라이브 연결 실패! 오류 코드: {error_num}\n{error_msg}"
                    if external_access:
                        error_message += "\n\n외부 접속(WebDAV) 연결 실패 가능성:\n1. 인터넷 연결을 확인하세요.\n2. VPN 연결이 필요할 수 있습니다.\n3. 외부 접속이 허용된 계정인지 확인하세요."
                    return error_message, 400

            # 오류 코드가 명확하지 않은 경우
            error_message = f"[{drive}:] 드라이브 연결 실패!\n{error_msg}"
            if external_access:
                error_message += "\n\n외부 접속(WebDAV) 연결 실패 가능성:\n1. 인터넷 연결을 확인하세요.\n2. VPN 연결이 필요할 수 있습니다.\n3. 외부 접속이 허용된 계정인지 확인하세요."
            return error_message, 400

    except Exception as e:
        return f"예외 발생: {e}", 500

# --- 네트워크 드라이브 해제 (로컬 실행) ---
@app.route('/logout', methods=['POST'])
def logout_drive():
    data = request.get_json()
    drive_raw = data.get('drive', '')

    drive, drive_error = validate_drive(drive_raw)
    if drive_error:
        return drive_error, 400

    try:
        result = subprocess.run(['net', 'use', f'{drive}:', '/delete', '/yes'],
                                capture_output=True, text=True,
                                creationflags=_NO_WINDOW)
        if result.returncode == 0:
            return f"[{drive}:] 로그아웃 성공!\n{result.stdout}"
        else:
            return f"[{drive}:] 로그아웃 실패!\n에러: {result.stderr}", 400
    except Exception as e:
        return f"예외 발생: {e}", 500

# --- 드라이브 경로를 웹에서 여는 엔드포인트 ---
@app.route('/open_drive/<drive_letter>', methods=['GET'])
def open_drive(drive_letter):
    drive, drive_error = validate_drive(drive_letter)
    if drive_error:
        return jsonify({"success": False, "message": drive_error}), 400

    try:
        open_explorer_maximized(f"{drive}:")
        return jsonify({
            "success": True,
            "message": f"{drive}: 드라이브를 탐색기에서 열었습니다."
        })
    except Exception as e:
        return jsonify({
            "success": False,
            "message": f"드라이브 열기 실패: {str(e)}"
        }), 500

# --- 현재 연결 상태 확인 (G, Q, R 드라이브 체크) ---
def get_connected_drives():
    try:
        result = subprocess.run(['net', 'use'], capture_output=True, text=True,
                                creationflags=_NO_WINDOW)
        if result.returncode != 0:
            return None, result.stderr
        output = result.stdout
        drives_status = {}
        for drive in VALID_DRIVES:
            pattern = rf'^OK\s+{drive}:'
            if re.search(pattern, output, re.MULTILINE):
                drives_status[drive] = True
            else:
                drives_status[drive] = False
        return drives_status, None
    except Exception as e:
        return None, str(e)

@app.route('/status', methods=['GET'])
def status():
    drives_status, error = get_connected_drives()
    if error:
        return jsonify({"error": error}), 500
    return jsonify(drives_status)

# 최대화된 탐색기 열기 함수
def open_explorer_maximized(drive_path):
    """
    지정된 드라이브 경로로 Explorer 창을 열고 최대화합니다.

    Args:
        drive_path: 열 드라이브 경로 (예: "G:")
    """
    try:
        # Explorer 실행
        subprocess.Popen(['explorer', drive_path], creationflags=_NO_WINDOW)

        # Win32 API를 사용하여 창을 찾고 최대화
        time.sleep(0.5)  # Explorer가 시작할 시간을 줍니다

        def enum_windows_callback(hwnd, result_list):
            if win32gui.IsWindowVisible(hwnd):
                # 창 제목 가져오기
                window_text = win32gui.GetWindowText(hwnd)
                # Explorer 창인지 확인 (드라이브 경로 또는 클래스 이름으로)
                if drive_path in window_text or "내 PC" in window_text:
                    result_list.append(hwnd)

                # 또는 창 클래스가 Explorer인지 확인
                class_name = win32gui.GetClassName(hwnd)
                if class_name == "CabinetWClass":  # Explorer 창의 클래스 이름
                    result_list.append(hwnd)

        found_windows = []
        win32gui.EnumWindows(enum_windows_callback, found_windows)

        for hwnd in found_windows:
            try:
                # 최소화되어 있으면 복원
                if win32gui.IsIconic(hwnd):
                    win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)

                # 창 최대화
                win32gui.ShowWindow(hwnd, win32con.SW_MAXIMIZE)

                # 창을 앞으로 가져오기
                win32gui.SetForegroundWindow(hwnd)

                # 창 깜빡이기
                win32gui.FlashWindow(hwnd, True)

                # 첫 번째 창을 찾아 처리했으면 종료
                return
            except Exception:
                pass

    except Exception:
        pass

def run_app():
    app.run(debug=False)

# 클라이언트에서 원격 모듈로 로드할 때는 __name__=='__main__' 블록을 제거하거나 run_app() 호출 대신 정의만 남겨두세요.
if __name__ == '__main__':
    run_app()
