#include "M5StackWiFiUploader.h"
#include <WiFi.h>
#include <cstdarg>

// ============================================================================
// コンストラクタ・デストラクタ
// ============================================================================

M5StackWiFiUploader::M5StackWiFiUploader(uint16_t port)
    : _webServer(nullptr),
#if ENABLE_WEBSOCKET
      _wsHandler(nullptr),
#endif
      _port(port),
      _isRunning(false),
      _uploadPath("/uploads"),
      _maxFileSize(50 * 1024 * 1024),  // 50MB
      _debugLevel(1),
#if ENABLE_WEBSOCKET
      _webSocketEnabled(false),
#endif
      _overwriteProtection(false),
      _totalUploaded(0),
      _nextSessionId(0),
      _onUploadStart(nullptr),
      _onUploadProgress(nullptr),
      _onUploadComplete(nullptr),
      _onUploadError(nullptr) {
    // デフォルト許可拡張子を設定
    _allowedExtensions = {
        "jpg", "jpeg", "png", "gif", "bmp",
        "bin", "dat", "txt", "csv", "json",
        "zip", "rar", "7z", "tar", "gz"
    };
}

M5StackWiFiUploader::~M5StackWiFiUploader() {
    end();
}

// ============================================================================
// 初期化・制御
// ============================================================================

bool M5StackWiFiUploader::begin(uint16_t port, const char* uploadPath) {
#if ENABLE_WEBSOCKET
    if (_webSocketEnabled) {
        _wsHandler = new WebSocketHandler(DEFAULT_WS_PORT);
        _wsHandler->begin();
        _wsHandler->onData([this](uint8_t clientId, const uint8_t* data, size_t length) {
            // WebSocketデータ受信処理
        });
    }
#endif
    _port = port;
    _uploadPath = uploadPath;

    // WebServerインスタンスを作成
    if (_webServer != nullptr) {
        delete _webServer;
    }
    _webServer = new WebServer(_port);

    if (_webServer == nullptr) {
        _log(1, "Failed to create WebServer instance");
        return false;
    }

    // ルートハンドラーを登録
    _webServer->on("/", HTTP_GET, [this]() { _handleRoot(); });
    _webServer->on("/api/upload", HTTP_POST, 
        [this]() { _handleUploadHTTP(); },
        [this]() { _handleUploadData(); }
    );
    _webServer->on("/api/files", HTTP_GET, [this]() { _handleListFiles(); });
#if ENABLE_ADVANCED_ENDPOINTS
    _webServer->on("/api/files/list", HTTP_GET, [this]() { _handleFileListDetailed(); });
    _webServer->on("/api/download", HTTP_GET, [this]() { _handleFileDownload(); });
#endif
    _webServer->on("/api/delete", HTTP_DELETE, [this]() { _handleDeleteFile(); });
    _webServer->on("/api/delete", HTTP_POST, [this]() { _handleDeleteFile(); });
    _webServer->on("/api/status", HTTP_GET, [this]() { _handleStatus(); });
#if ENABLE_ADVANCED_ENDPOINTS
    _webServer->on("/api/debug", HTTP_POST, [this]() { _handleDebugLog(); });
#endif

    // アップロードディレクトリを確認・作成
    if (!_ensureUploadDirectory()) {
        _log(1, "Failed to create upload directory: %s", _uploadPath.c_str());
        return false;
    }

    // サーバーを開始
    _webServer->begin();
    _isRunning = true;

    _log(3, "M5StackWiFiUploader started on port %d", _port);
    _log(3, "Upload path: %s", _uploadPath.c_str());
    _log(3, "Server URL: http://%s:%d", WiFi.localIP().toString().c_str(), _port);

    return true;
}

void M5StackWiFiUploader::handleClient() {
    if (!_isRunning) return;
    if (_webServer) _webServer->handleClient();
#if ENABLE_WEBSOCKET
    if (_wsHandler) _wsHandler->handleClient();
#endif
}

void M5StackWiFiUploader::end() {
#if ENABLE_WEBSOCKET
    if (_wsHandler) {
        _wsHandler->end();
        delete _wsHandler;
        _wsHandler = nullptr;
    }
#endif
    if (_webServer != nullptr) {
        _closeAllSessions();
        _webServer->stop();
        delete _webServer;
        _webServer = nullptr;
        _isRunning = false;
        _log(3, "M5StackWiFiUploader stopped");
    }
}

// ============================================================================
// 設定
// ============================================================================

void M5StackWiFiUploader::setMaxFileSize(uint32_t maxSize) {
    _maxFileSize = maxSize;
    _log(3, "Max file size set to %d bytes", maxSize);
}

void M5StackWiFiUploader::setAllowedExtensions(const char** extensions, uint8_t count) {
    _allowedExtensions.clear();
    for (uint8_t i = 0; i < count; i++) {
        _allowedExtensions.push_back(extensions[i]);
    }
    _log(3, "Allowed extensions updated: %d types", count);
}

void M5StackWiFiUploader::setUploadPath(const char* path) {
    _uploadPath = path;
    _ensureUploadDirectory();
    _log(3, "Upload path set to: %s", path);
}

void M5StackWiFiUploader::setDebugLevel(uint8_t level) {
    _debugLevel = level;
}

void M5StackWiFiUploader::enableWebSocket(bool enable) {
#if ENABLE_WEBSOCKET
    _webSocketEnabled = enable;
    _log(3, "WebSocket %s", enable ? "enabled" : "disabled");
#else
    _log(2, "WebSocket not available in LITE_MODE");
#endif
}

void M5StackWiFiUploader::setOverwriteProtection(bool enable) {
    _overwriteProtection = enable;
    _log(3, "Overwrite protection %s", enable ? "enabled" : "disabled");
}

// ============================================================================
// ステータス取得
// ============================================================================

uint8_t M5StackWiFiUploader::getActiveUploads() const {
    uint8_t count = 0;
    for (const auto& session : _activeSessions) {
        if (session.second.isActive) {
            count++;
        }
    }
    return count;
}

String M5StackWiFiUploader::getServerIP() const {
    return WiFi.localIP().toString();
}

String M5StackWiFiUploader::getServerURL() const {
    return "http://" + WiFi.localIP().toString() + ":" + String(_port);
}

uint32_t M5StackWiFiUploader::getSDFreeSpace() const {
    if (!SD.begin()) return 0;
    uint32_t total = SD.totalBytes();
    uint32_t used = SD.usedBytes();
    return (total > used) ? (total - used) : 0;
}

uint32_t M5StackWiFiUploader::getSDTotalSpace() const {
    if (!SD.begin()) return 0;
    return SD.totalBytes();
}

bool M5StackWiFiUploader::fileExists(const char* filename) const {
    String fullPath = _uploadPath + "/" + filename;
    return SD.exists(fullPath.c_str());
}

bool M5StackWiFiUploader::deleteFile(const char* filename) {
    String fullPath = _uploadPath + "/" + filename;
    Serial.printf("[DEBUG] deleteFile called with: '%s'\n", filename);
    Serial.printf("[DEBUG] Full path to delete: '%s'\n", fullPath.c_str());
    Serial.printf("[DEBUG] File exists before delete: %s\n", SD.exists(fullPath.c_str()) ? "true" : "false");
    
    if (SD.remove(fullPath.c_str())) {
        Serial.printf("[DEBUG] SD.remove() returned true for: %s\n", fullPath.c_str());
        Serial.printf("[DEBUG] File exists after delete: %s\n", SD.exists(fullPath.c_str()) ? "true" : "false");
        _log(3, "File deleted: %s", filename);
        return true;
    } else {
        Serial.printf("[DEBUG] SD.remove() returned false for: %s\n", fullPath.c_str());
        _log(2, "Failed to delete file: %s", filename);
        return false;
    }
}

std::vector<String> M5StackWiFiUploader::listFiles(const char* path) {
    std::vector<String> files;
    const char* searchPath = path ? path : _uploadPath.c_str();
    
    File dir = SD.open(searchPath);
    if (!dir || !dir.isDirectory()) {
        _log(2, "Failed to open directory: %s", searchPath);
        return files;
    }

    File file = dir.openNextFile();
    while (file) {
        if (!file.isDirectory()) {
            files.push_back(file.name());
        }
        file = dir.openNextFile();
    }
    dir.close();

    _log(3, "Listed %d files in %s", files.size(), searchPath);
    return files;
}

// ============================================================================
// プライベートメソッド - HTTPハンドラー
// ============================================================================

void M5StackWiFiUploader::_handleRoot() {
    String html = R"RAWHTML(
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WiFi File Uploader</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
        .container { max-width: 1000px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
        h1 { color: #333; display: flex; justify-content: space-between; align-items: center; }
        h2 { color: #555; margin-top: 30px; }
        .language-selector { margin-bottom: 20px; text-align: right; }
        .language-selector button { background: #607D8B; padding: 8px 16px; margin: 0 2px; }
        .language-selector button.active { background: #2196F3; }
        .upload-area { border: 2px dashed #ccc; padding: 20px; text-align: center; margin: 20px 0; border-radius: 4px; cursor: pointer; }
        .upload-area:hover { background: #f9f9f9; }
        .upload-area.dragover { background: #e3f2fd; border-color: #2196F3; }
        input[type="file"] { display: none; }
        button { background: #2196F3; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; margin: 2px; }
        button:hover { background: #1976D2; }
        button.danger { background: #f44336; }
        button.danger:hover { background: #d32f2f; }
        button.success { background: #4CAF50; }
        button.success:hover { background: #388E3C; }
        .file-list { margin-top: 20px; }
        .file-table { width: 100%; border-collapse: collapse; }
        .file-table th, .file-table td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
        .file-table th { background: #f5f5f5; font-weight: bold; }
        .file-table tr:hover { background: #f9f9f9; }
        .file-name { font-weight: 500; color: #2196F3; cursor: pointer; }
        .file-name:hover { text-decoration: underline; }
        .file-size { color: #666; }
        .file-date { color: #999; font-size: 0.9em; }
        .progress { width: 100%; height: 20px; background: #e0e0e0; border-radius: 4px; margin: 10px 0; overflow: hidden; }
        .progress-bar { height: 100%; background: #4CAF50; width: 0%; transition: width 0.3s; }
        .status { padding: 10px; margin: 10px 0; border-radius: 4px; }
        .status.info { background: #e3f2fd; color: #1976D2; }
        .status.success { background: #e8f5e9; color: #388E3C; }
        .status.error { background: #ffebee; color: #C62828; }
        .actions { display: flex; gap: 5px; }
        .no-files { text-align: center; padding: 20px; color: #999; }
        .loading { text-align: center; padding: 20px; }
        
        /* モバイル対応 */
        @media (max-width: 768px) {
            body { margin: 10px; }
            .container { padding: 15px; }
            .file-table { 
                display: block; 
                overflow-x: auto; 
                -webkit-overflow-scrolling: touch; 
                font-size: 12px; 
            }
            .file-table th, .file-table td { 
                padding: 8px 4px; 
                font-size: 12px; 
            }
            .file-name { 
                word-break: break-all; 
                max-width: 150px; 
            }
            .actions { 
                flex-direction: column; 
                gap: 2px; 
            }
            button { 
                padding: 6px 8px; 
                font-size: 11px; 
                margin: 1px 0; 
            }
            .upload-area { padding: 15px; }
            h1 { font-size: 24px; }
            h2 { font-size: 20px; }
        }
        
        @media (max-width: 480px) {
            .file-table th, .file-table td { 
                padding: 6px 2px; 
                font-size: 11px; 
            }
            .file-name { 
                max-width: 120px; 
            }
            button { 
                padding: 4px 6px; 
                font-size: 10px; 
            }
            .container { padding: 10px; }
            .upload-area { padding: 10px; }
        }
        
            </style>
</head>
<body>
    <div class="container">
        <div class="language-selector">
            <button onclick="setLanguage('ja')" id="lang-ja" class="active">日本語</button>
            <button onclick="setLanguage('en')" id="lang-en">English</button>
        </div>
        
        <h1 id="title">WiFi File Uploader</h1>
        <p id="description">ファイルをドラッグ&ドロップするか、下のボタンをクリックしてアップロードしてください。</p>
        
        <div class="upload-area" id="uploadArea">
            <p>ここにファイルをドラッグ&ドロップ</p>
            <input type="file" id="fileInput" multiple>
            <button onclick="document.getElementById('fileInput').click()">ファイルを選択</button>
        </div>

        <div id="status"></div>
        <div id="uploadProgress"></div>

        <div class="file-list">
            <h2 id="fileListTitle">SDカード内のファイル</h2>
            <button onclick="loadFilesList()" class="success" id="refreshBtn">更新</button>
            <div id="filesList" class="loading">読み込み中...</div>
        </div>
    </div>

    <script>
        // 多言語対応
        const translations = {
            ja: {
                title: 'WiFi File Uploader',
                description: 'ファイルをドラッグ&ドロップするか、下のボタンをクリックしてアップロードしてください。',
                dropText: 'ここにファイルをドラッグ&ドロップ',
                selectFile: 'ファイルを選択',
                fileListTitle: 'SDカード内のファイル',
                refresh: '更新',
                loading: '読み込み中...',
                noFiles: 'ファイルがありません',
                download: 'ダウンロード',
                delete: '削除',
                downloading: 'をダウンロード中...',
                deleting: 'を削除中...',
                uploadSuccess: 'ファイルが正常にアップロードされました',
                uploadError: 'ファイルのアップロードに失敗しました',
                deleteSuccess: 'ファイルが正常に削除されました',
                deleteError: 'ファイルの削除に失敗しました',
                listError: 'ファイル一覧の取得に失敗しました'
            },
            en: {
                title: 'WiFi File Uploader',
                description: 'Drag and drop files here or click the button below to upload.',
                dropText: 'Drag and drop files here',
                selectFile: 'Select Files',
                fileListTitle: 'Files on SD Card',
                refresh: 'Refresh',
                loading: 'Loading...',
                noFiles: 'No files',
                download: 'Download',
                delete: 'Delete',
                downloading: ' downloading...',
                deleting: ' deleting...',
                uploadSuccess: 'File uploaded successfully',
                uploadError: 'Failed to upload file',
                deleteSuccess: 'File deleted successfully',
                deleteError: 'Failed to delete file',
                listError: 'Failed to get file list'
            }
        };

        let currentLang = 'ja';

        function setLanguage(lang) {
            currentLang = lang;
            
            // 言語ボタンの状態を更新
            document.getElementById('lang-ja').classList.toggle('active', lang === 'ja');
            document.getElementById('lang-en').classList.toggle('active', lang === 'en');
            
            // テキストを更新
            const t = translations[lang];
            document.getElementById('title').textContent = t.title;
            document.getElementById('description').textContent = t.description;
            document.querySelector('#uploadArea p').textContent = t.dropText;
            document.querySelector('#uploadArea button').textContent = t.selectFile;
            document.getElementById('fileListTitle').textContent = t.fileListTitle;
            document.getElementById('refreshBtn').textContent = t.refresh;
            
            // ファイル一覧を再読み込み
            loadFilesList();
        }

        const uploadArea = document.getElementById('uploadArea');
        const fileInput = document.getElementById('fileInput');
        const statusDiv = document.getElementById('status');
        const progressDiv = document.getElementById('uploadProgress');
        const filesListDiv = document.getElementById('filesList');

        uploadArea.addEventListener('dragover', (e) => {
            e.preventDefault();
            uploadArea.classList.add('dragover');
        });

        uploadArea.addEventListener('dragleave', () => {
            uploadArea.classList.remove('dragover');
        });

        uploadArea.addEventListener('drop', (e) => {
            e.preventDefault();
            uploadArea.classList.remove('dragover');
            handleFiles(e.dataTransfer.files);
        });

        fileInput.addEventListener('change', (e) => {
            handleFiles(e.target.files);
        });

        function handleFiles(files) {
            for (let file of files) {
                uploadFile(file);
            }
        }

        function uploadFile(file) {
            const formData = new FormData();
            formData.append('file', file);

            const progressId = 'progress-' + Date.now();
            const progressHTML = `
                <div id="${progressId}">
                    <p>${file.name} (${formatFileSize(file.size)})</p>
                    <div class="progress">
                        <div class="progress-bar" id="${progressId}-bar"></div>
                    </div>
                </div>
            `;
            progressDiv.innerHTML += progressHTML;

            const xhr = new XMLHttpRequest();
            
            xhr.upload.addEventListener('progress', (e) => {
                if (e.lengthComputable) {
                    const percentComplete = (e.loaded / e.total) * 100;
                    document.getElementById(progressId + '-bar').style.width = percentComplete + '%';
                }
            });

            xhr.addEventListener('load', () => {
                if (xhr.status === 200) {
                    const response = JSON.parse(xhr.responseText);
                    const t = translations[currentLang];
                    showStatus('success', `${file.name} ${t.uploadSuccess}`);
                    document.getElementById(progressId).remove();
                    loadFilesList();
                } else {
                    const t = translations[currentLang];
                    showStatus('error', `${file.name} ${t.uploadError}: ${xhr.status}`);
                }
            });

            xhr.addEventListener('error', () => {
                const t = translations[currentLang];
                showStatus('error', `${file.name} ${t.uploadError}`);
            });

            xhr.open('POST', '/api/upload');
            xhr.send(formData);
        }

        function loadFilesList() {
            const t = translations[currentLang];
            filesListDiv.innerHTML = `<div class="loading">${t.loading}</div>`;
            
            fetch('/api/files/list')
                .then(response => response.json())
                .then(data => {
                    filesListDiv.innerHTML = '';
                    if (data.files && data.files.length > 0) {
                        const table = document.createElement('table');
                        table.className = 'file-table';
                        table.innerHTML = `
                            <thead>
                                <tr>
                                    <th>${currentLang === 'ja' ? 'ファイル名' : 'Filename'}</th>
                                    <th>${currentLang === 'ja' ? 'サイズ' : 'Size'}</th>
                                    <th>${currentLang === 'ja' ? '更新日時' : 'Modified'}</th>
                                    <th>${currentLang === 'ja' ? '操作' : 'Actions'}</th>
                                </tr>
                            </thead>
                            <tbody id="filesTableBody"></tbody>
                        `;
                        filesListDiv.appendChild(table);
                        
                        const tbody = document.getElementById('filesTableBody');
                        data.files.forEach(file => {
                            const row = document.createElement('tr');
                            row.innerHTML = `
                                <td><span class="file-name" onclick="downloadFile('${file.name}')">${file.name}</span></td>
                                <td class="file-size">${formatFileSize(file.size)}</td>
                                <td class="file-date">${formatDate(file.modified)}</td>
                                <td class="actions">
                                    <button onclick="downloadFile('${file.name}')" class="success">${t.download}</button>
                                    <button onclick="deleteFile('${file.name}')" class="danger">${t.delete}</button>
                                </td>
                            `;
                            tbody.appendChild(row);
                        });
                    } else {
                        filesListDiv.innerHTML = `<div class="no-files">${t.noFiles}</div>`;
                    }
                })
                .catch(error => {
                    const t = translations[currentLang];
                    filesListDiv.innerHTML = `<div class="no-files">${t.listError}</div>`;
                    showStatus('error', t.listError);
                });
        }

        function downloadFile(filename) {
            const t = translations[currentLang];
            window.location.href = `/api/download?filename=${encodeURIComponent(filename)}`;
            showStatus('info', `${filename}${t.downloading}`);
        }

        function deleteFile(filename) {
            const t = translations[currentLang];
            console.log(`[DEBUG] deleteFile called with: ${filename}`);
            
if (confirm(currentLang === 'ja' ? `${filename} を削除しますか？` : `Delete ${filename}?`)) {
                const url = `/api/delete?filename=${encodeURIComponent(filename)}`;
                console.log(`[DEBUG] Sending DELETE request to: ${url}`);
                
                fetch(url, { 
                    method: 'DELETE',
                    headers: {
                        'Content-Type': 'application/json'
                    }
                })
                .then(response => {
                    console.log(`[DEBUG] Response status: ${response.status}`);
                    return response.json();
                })
                .then(data => {
                    console.log(`[DEBUG] Response data:`, data);
                    if (data.success) {
                        showStatus('success', `${filename} ${t.deleteSuccess}`);
                        loadFilesList();
                    } else {
                        showStatus('error', `${filename} ${t.deleteError}`);
                    }
                })
                .catch(error => {
                    console.error(`[DEBUG] Fetch error:`, error);
                    showStatus('error', `${filename} ${t.deleteError}`);
                });
            }
        }

        function formatFileSize(bytes) {
            if (bytes === 0) return '0 B';
            const k = 1024;
            const sizes = ['B', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
        }

        function formatDate(timestamp) {
            if (!timestamp || timestamp === 0) return '-';
            const date = new Date(timestamp * 1000);
            const year = date.getFullYear();
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');
            const hours = String(date.getHours()).padStart(2, '0');
            const minutes = String(date.getMinutes()).padStart(2, '0');
            return `${year}-${month}-${day} ${hours}:${minutes}`;
        }

        function showStatus(type, message) {
            const status = document.createElement('div');
            status.className = 'status ' + type;
            status.textContent = message;
            statusDiv.insertBefore(status, statusDiv.firstChild);
            setTimeout(() => status.remove(), 5000);
        }

        loadFilesList();
    </script>
</body>
</html>
    )RAWHTML";
    
    _webServer->send(200, "text/html; charset=utf-8", html);
}

void M5StackWiFiUploader::_handleUploadHTTP() {
    // マルチパートアップロードが完了した後に呼ばれる
    Serial.println("[DEBUG] _handleUploadHTTP called");
    _sendJSONResponse(true, "File uploaded successfully");
}

void M5StackWiFiUploader::_handleUploadData() {
    HTTPUpload& upload = _webServer->upload();
    static File uploadFile;
    static String currentFilename;
    static uint32_t currentFilesize;
    static uint32_t lastProgressSize = 0;
    static uint32_t lastFlushSize = 0;
    
    if (upload.status == UPLOAD_FILE_START) {
        // 既存のファイルハンドルが開いている場合はクローズ
        if (uploadFile) {
            uploadFile.close();
        }
        
        currentFilename = upload.filename;
        currentFilesize = 0;
        
        // 進捗カウンタとフラッシュカウンタをリセット
        lastProgressSize = 0;
        lastFlushSize = 0;
        
        // SDカード空き容量を確認
        uint64_t cardSize = SD.cardSize() / (1024 * 1024);
        uint64_t usedBytes = SD.usedBytes() / (1024 * 1024);
        uint64_t totalBytes = SD.totalBytes() / (1024 * 1024);
        uint64_t freeBytes = totalBytes - usedBytes;
        
        // ヒープメモリ状況を確認
        uint32_t freeHeap = ESP.getFreeHeap();
        uint32_t minFreeHeap = ESP.getMinFreeHeap();
        
        _log(3, "Upload Start: %s", currentFilename.c_str());
        _log(3, "SD Card - Total: %llu MB, Used: %llu MB, Free: %llu MB", totalBytes, usedBytes, freeBytes);
        _log(3, "Heap - Free: %u bytes, Min Free: %u bytes", freeHeap, minFreeHeap);
        
        // ファイル名を検証
        if (!_isValidFilename(currentFilename.c_str())) {
            _log(2, "Invalid filename: %s", currentFilename.c_str());
            return;
        }
        
        currentFilename = _sanitizeFilename(currentFilename.c_str());
        
        // 拡張子を検証
        if (!_isValidExtension(currentFilename.c_str())) {
            _log(2, "Invalid file extension: %s", currentFilename.c_str());
            return;
        }
        
        // ファイルパスを作成
        String fullPath = _uploadPath + "/" + currentFilename;
        
        // 上書き保護をチェック
        if (_overwriteProtection && SD.exists(fullPath.c_str())) {
            _log(2, "File already exists (overwrite protection): %s", currentFilename.c_str());
            return;
        }
        
        // ファイルを開く
        uploadFile = SD.open(fullPath.c_str(), FILE_WRITE);
        if (!uploadFile) {
            _log(1, "Failed to open file for writing: %s", fullPath.c_str());
            return;
        }
        
        // コールバック: アップロード開始
        // 注: upload.totalSizeはマルチパートの全体サイズなので、個別ファイルサイズとしては使えない
        if (_onUploadStart != nullptr) {
            _onUploadStart(currentFilename.c_str(), 0);  // サイズは不明なので0を渡す
        }
        
    } else if (upload.status == UPLOAD_FILE_WRITE) {
        if (uploadFile) {
            // ファイルサイズをチェック
            currentFilesize += upload.currentSize;
            if (currentFilesize > _maxFileSize) {
                _log(2, "File too large: %d bytes (max: %d)", currentFilesize, _maxFileSize);
                uploadFile.close();
                String fullPath = _uploadPath + "/" + currentFilename;
                SD.remove(fullPath.c_str());
                return;
            }
            
            // データを書き込み
            size_t written = uploadFile.write(upload.buf, upload.currentSize);
            if (written != upload.currentSize) {
                uint32_t freeHeap = ESP.getFreeHeap();
                _log(1, "Write error: expected %d, wrote %d (Free heap: %u bytes)", upload.currentSize, written, freeHeap);
                uploadFile.close();
                String fullPath = _uploadPath + "/" + currentFilename;
                SD.remove(fullPath.c_str());
                
                // コールバック: エラー
                if (_onUploadError) {
                    _onUploadError(currentFilename.c_str(), ERR_SD_WRITE_FAILED, "SD write failed");
                }
                return;
            }
            
            // 256KBごとにflushしてSDカードへの書き込みを確実にする
            if (currentFilesize - lastFlushSize >= 262144 || currentFilesize < lastFlushSize) {
                uploadFile.flush();
                delay(10);  // SDカードの書き込み完了を待つ
                lastFlushSize = currentFilesize;
            }
            
            // コールバック: 進捗 (64KBごとに呼び出してメモリ負荷を軽減)
            if (_onUploadProgress && (currentFilesize - lastProgressSize >= 65536 || currentFilesize < lastProgressSize)) {
                _onUploadProgress(currentFilename.c_str(), currentFilesize, currentFilesize);
                lastProgressSize = currentFilesize;
            }
        }
        
    } else if (upload.status == UPLOAD_FILE_END) {
        if (uploadFile) {
            uploadFile.close();
            _totalUploaded += currentFilesize;
            _log(3, "Upload Complete: %s (%d bytes)", currentFilename.c_str(), currentFilesize);
            
            // コールバック: アップロード完了
            if (_onUploadComplete) {
                _onUploadComplete(currentFilename.c_str(), currentFilesize, true);
            }
        }
        
    } else if (upload.status == UPLOAD_FILE_ABORTED) {
        if (uploadFile) {
            uploadFile.close();
            String fullPath = _uploadPath + "/" + currentFilename;
            SD.remove(fullPath.c_str());
            _log(2, "Upload Aborted: %s", currentFilename.c_str());
            
            // コールバック: エラー
            if (_onUploadError) {
                _onUploadError(currentFilename.c_str(), ERR_UNKNOWN, "Upload aborted");
            }
        }
    }
}

void M5StackWiFiUploader::_handleListFiles() {
    std::vector<String> files = listFiles();
    
    String json = "{\"success\": true, \"files\": [";
    for (size_t i = 0; i < files.size(); i++) {
        json += "\"" + files[i] + "\"";
        if (i < files.size() - 1) {
            json += ", ";
        }
    }
    json += "]}";

    _webServer->send(200, "application/json", json);
}

void M5StackWiFiUploader::_handleDeleteFile() {
    Serial.println("[DEBUG] _handleDeleteFile called");
    Serial.printf("[DEBUG] HTTP method: %d\n", _webServer->method());
    
    if (_webServer->method() != HTTP_DELETE && _webServer->method() != HTTP_POST) {
        Serial.println("[DEBUG] Method not allowed");
        _sendJSONResponse(false, "Method not allowed");
        return;
    }

    String filename;
    
    // DELETEメソッドの場合はクエリパラメータから取得
    if (_webServer->method() == HTTP_DELETE) {
        filename = _webServer->arg("filename");
        Serial.printf("[DEBUG] DELETE method, filename from query: '%s'\n", filename.c_str());
    } else {
        // POSTメソッドの場合はJSONボディから取得
        String body = _webServer->arg("plain");
        Serial.printf("[DEBUG] POST method, body: '%s'\n", body.c_str());
        
        int filenamePos = body.indexOf("\"filename\"");
        if (filenamePos == -1) {
            Serial.println("[DEBUG] No filename in JSON body");
            _sendJSONResponse(false, "No filename provided");
            return;
        }
        
        int startPos = body.indexOf("\"", filenamePos + 11) + 1;
        int endPos = body.indexOf("\"", startPos);
        filename = body.substring(startPos, endPos);
        Serial.printf("[DEBUG] Extracted filename: '%s'\n", filename.c_str());
    }

    if (filename.length() == 0) {
        Serial.println("[DEBUG] Empty filename");
        _sendJSONResponse(false, "No filename provided");
        return;
    }

    // ファイル名を検証
    if (!_isValidFilename(filename.c_str())) {
        Serial.printf("[DEBUG] Invalid filename: '%s'\n", filename.c_str());
        _sendJSONResponse(false, "Invalid filename");
        return;
    }

    String fullPath = _uploadPath + "/" + filename;
    Serial.printf("[DEBUG] Full path: '%s'\n", fullPath.c_str());
    Serial.printf("[DEBUG] File exists: %s\n", SD.exists(fullPath.c_str()) ? "true" : "false");

    if (deleteFile(filename.c_str())) {
        Serial.printf("[DEBUG] File deleted successfully: %s\n", filename.c_str());
        _sendJSONResponse(true, "File deleted successfully", filename.c_str());
    } else {
        Serial.printf("[DEBUG] Failed to delete file: %s\n", filename.c_str());
        _sendJSONResponse(false, "Failed to delete file");
    }
}

void M5StackWiFiUploader::_handleStatus() {
    String json = "{";
    json += "\"running\": " + String(_isRunning ? "true" : "false") + ", ";
    json += "\"activeUploads\": " + String(getActiveUploads()) + ", ";
    json += "\"totalUploaded\": " + String(_totalUploaded) + ", ";
    json += "\"sdFreeSpace\": " + String(getSDFreeSpace()) + ", ";
    json += "\"sdTotalSpace\": " + String(getSDTotalSpace()) + ", ";
    json += "\"serverIP\": \"" + getServerIP() + "\", ";
    json += "\"serverPort\": " + String(_port);
    json += "}";

    _webServer->send(200, "application/json", json);
}

#if ENABLE_ADVANCED_ENDPOINTS
void M5StackWiFiUploader::_handleFileListDetailed() {
    _log(3, "Handling detailed file list request");
    
    std::vector<FileInfo> files = SDCardManager::listFilesWithInfo(_uploadPath.c_str(), false);
    
    String json = "{";
    json += "\"files\": [";
    
    for (size_t i = 0; i < files.size(); i++) {
        if (i > 0) json += ", ";
        json += "{";
        json += "\"name\": \"" + files[i].name + "\", ";
        json += "\"size\": " + String(files[i].size) + ", ";
        json += "\"modified\": " + String(files[i].modified) + ", ";
        json += "\"isDirectory\": " + String(files[i].isDirectory ? "true" : "false") + ", ";
        json += "\"extension\": \"" + files[i].extension + "\"";
        json += "}";
    }
    
    json += "], ";
    json += "\"total\": " + String(files.size());
    json += "}";
    
    _webServer->send(200, "application/json", json);
}
#endif

#if ENABLE_ADVANCED_ENDPOINTS
void M5StackWiFiUploader::_handleFileDownload() {
    if (!_webServer->hasArg("filename")) {
        _sendJSONResponse(false, "Missing filename parameter", nullptr);
        return;
    }
    
    String filename = _webServer->arg("filename");
    String fullPath = _uploadPath + "/" + filename;
    
    _log(3, "Download request for: %s", filename.c_str());
    
    // パストラバーサル攻撃を防止
    if (filename.indexOf("..") >= 0 || filename.indexOf("/") >= 0 || filename.indexOf("\\") >= 0) {
        _log(1, "Invalid filename (path traversal attempt): %s", filename.c_str());
        _sendJSONResponse(false, "Invalid filename", filename.c_str());
        return;
    }
    
    if (!SDCardManager::fileExists(fullPath.c_str())) {
        _log(1, "File not found: %s", fullPath.c_str());
        _sendJSONResponse(false, "File not found", filename.c_str());
        return;
    }
    
    File file = SD.open(fullPath.c_str(), FILE_READ);
    if (!file) {
        _log(1, "Failed to open file: %s", fullPath.c_str());
        _sendJSONResponse(false, "Failed to open file", filename.c_str());
        return;
    }
    
    String contentType = _getContentType(filename.c_str());
    size_t fileSize = file.size();
    
    _webServer->sendHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
    _webServer->streamFile(file, contentType);
    
    file.close();
    _log(3, "File download completed: %s (%d bytes)", filename.c_str(), fileSize);
}
#endif

// ============================================================================
// プライベートメソッド - ファイル操作
// ============================================================================

bool M5StackWiFiUploader::_saveFile(const char* filename, uint8_t* data, uint32_t size) {
    String fullPath = _uploadPath + "/" + filename;

    File file = SD.open(fullPath.c_str(), FILE_WRITE);
    if (!file) {
        _log(1, "Failed to open file for writing: %s", fullPath.c_str());
        return false;
    }

    uint32_t written = 0;
    uint32_t chunkSize = 4096;

    while (written < size) {
        uint32_t toWrite = (size - written) < chunkSize ? (size - written) : chunkSize;
        size_t result = file.write(data + written, toWrite);

        if (result != toWrite) {
            _log(1, "Failed to write data to file: %s", filename);
            file.close();
            return false;
        }

        written += toWrite;

        // コールバック: 進捗通知
        if (_onUploadProgress) {
            _onUploadProgress(filename, written, size);
        }
    }

    file.close();
    _log(3, "File saved successfully: %s (%d bytes)", fullPath.c_str(), size);
    return true;
}

bool M5StackWiFiUploader::_isValidExtension(const char* filename) {
    const char* ext = strrchr(filename, '.');
    if (!ext || ext == filename) {
        return false;
    }

    ext++;  // ドットをスキップ
    String extStr = String(ext);
    extStr.toLowerCase();

    for (const auto& allowed : _allowedExtensions) {
        if (extStr == allowed) {
            return true;
        }
    }

    return false;
}

bool M5StackWiFiUploader::_isValidFilename(const char* filename) {
    if (!filename || strlen(filename) == 0) {
        return false;
    }

    // 危険な文字をチェック
    const char* dangerous = "<>:\"|?*";
    for (const char* p = dangerous; *p; p++) {
        if (strchr(filename, *p)) {
            return false;
        }
    }

    return true;
}

String M5StackWiFiUploader::_sanitizeFilename(const char* filename) {
    String result = filename;
    
    // パス区切り文字を除去
    result.replace("/", "_");
    result.replace("\\", "_");
    result.replace("..", "");
    
    // 先頭のドットを除去（隠しファイル対策）
    while (result.startsWith(".")) {
        result = result.substring(1);
    }

    return result;
}

bool M5StackWiFiUploader::_ensureUploadDirectory() {
    if (!SD.exists(_uploadPath.c_str())) {
        if (!SD.mkdir(_uploadPath.c_str())) {
            _log(1, "Failed to create upload directory: %s", _uploadPath.c_str());
            return false;
        }
        _log(3, "Upload directory created: %s", _uploadPath.c_str());
    }
    return true;
}

// ============================================================================
// プライベートメソッド - ユーティリティ
// ============================================================================

void M5StackWiFiUploader::_log(uint8_t level, const char* format, ...) {
#if ENABLE_DEBUG_LOG
    if (level > _debugLevel) {
        return;
    }

    char buffer[256];
    va_list args;
    va_start(args, format);
    vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);

    const char* levelStr = "";
    switch (level) {
        case 1: levelStr = "[ERROR]"; break;
        case 2: levelStr = "[WARN]"; break;
        case 3: levelStr = "[INFO]"; break;
        case 4: levelStr = "[DEBUG]"; break;
        default: levelStr = "[LOG]"; break;
    }

    Serial.printf("%s %s\n", levelStr, buffer);
#endif
}

void M5StackWiFiUploader::_sendJSONResponse(bool success, const char* message, const char* filename) {
    String json = "{";
    json += "\"success\": " + String(success ? "true" : "false") + ", ";
    json += "\"message\": \"" + String(message) + "\"";
    
    if (filename) {
        json += ", \"filename\": \"" + String(filename) + "\"";
    }
    
    json += "}";

    _webServer->send(success ? 200 : 400, "application/json", json);
}

String M5StackWiFiUploader::_getContentType(const char* filename) {
    const char* ext = strrchr(filename, '.');
    if (!ext) return "application/octet-stream";

    ext++;
    String extStr = String(ext);
    extStr.toLowerCase();

    if (extStr == "jpg" || extStr == "jpeg") return "image/jpeg";
    if (extStr == "png") return "image/png";
    if (extStr == "gif") return "image/gif";
    if (extStr == "txt") return "text/plain";
    if (extStr == "json") return "application/json";
    if (extStr == "csv") return "text/csv";

    return "application/octet-stream";
}

// ============================================================================
// セッション管理（将来の拡張用）
// ============================================================================

uint8_t M5StackWiFiUploader::_createSession(const char* filename, uint32_t filesize) {
    uint8_t sessionId = _nextSessionId++;
    UploadSession session;
    session.filename = filename;
    session.filesize = filesize;
    session.uploaded = 0;
    session.startTime = millis();
    session.isActive = true;
    session.sessionId = sessionId;

    _activeSessions[sessionId] = session;
    return sessionId;
}

UploadSession* M5StackWiFiUploader::_getSession(uint8_t sessionId) {
    auto it = _activeSessions.find(sessionId);
    if (it != _activeSessions.end()) {
        return &it->second;
    }
    return nullptr;
}

void M5StackWiFiUploader::_closeSession(uint8_t sessionId) {
    auto it = _activeSessions.find(sessionId);
    if (it != _activeSessions.end()) {
        if (it->second.file) {
            it->second.file.close();
        }
        _activeSessions.erase(it);
    }
}

void M5StackWiFiUploader::_closeAllSessions() {
    for (auto& session : _activeSessions) {
        if (session.second.file) {
            session.second.file.close();
        }
    }
    _activeSessions.clear();
}

#if ENABLE_ADVANCED_ENDPOINTS
void M5StackWiFiUploader::_handleDebugLog() {
    String message = _webServer->arg("message");
    Serial.printf("[WEB_DEBUG] %s\n", message.c_str());
    _webServer->send(200, "text/plain", "OK");
}
#endif
