// Package main provides bug reporting functionality for EMLy.
// This file contains methods for creating bug reports with screenshots,
// email files, and system information.
package main

import (
	"archive/zip"
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"emly/backend/utils"
)

// =============================================================================
// Bug Report Types
// =============================================================================

// BugReportResult contains paths to the generated bug report files.
type BugReportResult struct {
	// FolderPath is the path to the bug report folder in temp
	FolderPath string `json:"folderPath"`
	// ScreenshotPath is the path to the captured screenshot file
	ScreenshotPath string `json:"screenshotPath"`
	// MailFilePath is the path to the copied mail file (empty if no mail loaded)
	MailFilePath string `json:"mailFilePath"`
}

// BugReportInput contains the user-provided bug report details.
type BugReportInput struct {
	// Name is the user's name
	Name string `json:"name"`
	// Email is the user's email address for follow-up
	Email string `json:"email"`
	// Description is the detailed bug description
	Description string `json:"description"`
	// ScreenshotData is the base64-encoded PNG screenshot (captured before dialog opens)
	ScreenshotData string `json:"screenshotData"`
	// LocalStorageData is the JSON-encoded localStorage data
	LocalStorageData string `json:"localStorageData"`
	// ConfigData is the JSON-encoded config.ini data
	ConfigData string `json:"configData"`
}

// SubmitBugReportResult contains the result of submitting a bug report.
type SubmitBugReportResult struct {
	// ZipPath is the path to the created zip file
	ZipPath string `json:"zipPath"`
	// FolderPath is the path to the bug report folder
	FolderPath string `json:"folderPath"`
	// Uploaded indicates whether the report was successfully uploaded to the server
	Uploaded bool `json:"uploaded"`
	// ReportID is the server-assigned report ID (0 if not uploaded)
	ReportID int64 `json:"reportId"`
	// UploadError contains the error message if upload failed (empty on success)
	UploadError string `json:"uploadError"`
}

// =============================================================================
// Bug Report Methods
// =============================================================================

// CreateBugReportFolder creates a folder in temp with screenshot and optionally
// the current mail file. This is used for the legacy bug report flow.
//
// Returns:
//   - *BugReportResult: Paths to created files
//   - error: Error if folder creation or file operations fail
func (a *App) CreateBugReportFolder() (*BugReportResult, error) {
	// Create unique folder name with timestamp
	timestamp := time.Now().Format("20060102_150405")
	folderName := fmt.Sprintf("emly_bugreport_%s", timestamp)

	// Create folder in temp directory
	tempDir := os.TempDir()
	bugReportFolder := filepath.Join(tempDir, folderName)

	if err := os.MkdirAll(bugReportFolder, 0755); err != nil {
		return nil, fmt.Errorf("failed to create bug report folder: %w", err)
	}

	result := &BugReportResult{
		FolderPath: bugReportFolder,
	}

	// Take and save screenshot
	screenshotResult, err := a.TakeScreenshot()
	if err != nil {
		return nil, fmt.Errorf("failed to take screenshot: %w", err)
	}

	screenshotData, err := base64.StdEncoding.DecodeString(screenshotResult.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to decode screenshot: %w", err)
	}

	screenshotPath := filepath.Join(bugReportFolder, screenshotResult.Filename)
	if err := os.WriteFile(screenshotPath, screenshotData, 0644); err != nil {
		return nil, fmt.Errorf("failed to save screenshot: %w", err)
	}
	result.ScreenshotPath = screenshotPath

	// Copy currently loaded mail file if one exists
	if a.CurrentMailFilePath != "" {
		mailData, err := os.ReadFile(a.CurrentMailFilePath)
		if err != nil {
			// Log but don't fail - screenshot is still valid
			Log("Failed to read mail file for bug report:", err)
		} else {
			mailFilename := filepath.Base(a.CurrentMailFilePath)
			mailFilePath := filepath.Join(bugReportFolder, mailFilename)

			if err := os.WriteFile(mailFilePath, mailData, 0644); err != nil {
				Log("Failed to copy mail file for bug report:", err)
			} else {
				result.MailFilePath = mailFilePath
			}
		}
	}

	return result, nil
}

// SubmitBugReport creates a complete bug report with user input, saves all files,
// and creates a zip archive ready for submission.
//
// The bug report includes:
//   - User-provided description (report.txt)
//   - Screenshot (captured before dialog opens)
//   - Currently loaded mail file (if any)
//   - localStorage data (localStorage.json)
//   - Config.ini data (config.json)
//   - System information (hostname, OS version, hardware ID)
//
// Parameters:
//   - input: User-provided bug report details including pre-captured screenshot, localStorage, and config data
//
// Returns:
//   - *SubmitBugReportResult: Paths to the zip file and folder
//   - error: Error if any file operation fails
func (a *App) SubmitBugReport(input BugReportInput) (*SubmitBugReportResult, error) {
	// Create unique folder name with timestamp
	timestamp := time.Now().Format("20060102_150405")
	folderName := fmt.Sprintf("emly_bugreport_%s", timestamp)

	// Create folder in temp directory
	tempDir := os.TempDir()
	bugReportFolder := filepath.Join(tempDir, folderName)

	if err := os.MkdirAll(bugReportFolder, 0755); err != nil {
		return nil, fmt.Errorf("failed to create bug report folder: %w", err)
	}

	// Save the pre-captured screenshot (captured before dialog opened)
	if input.ScreenshotData != "" {
		screenshotData, err := base64.StdEncoding.DecodeString(input.ScreenshotData)
		if err != nil {
			Log("Failed to decode screenshot:", err)
		} else {
			screenshotPath := filepath.Join(bugReportFolder, fmt.Sprintf("emly_screenshot_%s.png", timestamp))
			if err := os.WriteFile(screenshotPath, screenshotData, 0644); err != nil {
				Log("Failed to save screenshot:", err)
			}
		}
	}

	// Copy the mail file if one is loaded
	if a.CurrentMailFilePath != "" {
		mailData, err := os.ReadFile(a.CurrentMailFilePath)
		if err != nil {
			Log("Failed to read mail file for bug report:", err)
		} else {
			mailFilename := filepath.Base(a.CurrentMailFilePath)
			mailFilePath := filepath.Join(bugReportFolder, mailFilename)
			if err := os.WriteFile(mailFilePath, mailData, 0644); err != nil {
				Log("Failed to copy mail file for bug report:", err)
			}
		}
	}

	// Save localStorage data if provided
	if input.LocalStorageData != "" {
		localStoragePath := filepath.Join(bugReportFolder, "localStorage.json")
		if err := os.WriteFile(localStoragePath, []byte(input.LocalStorageData), 0644); err != nil {
			Log("Failed to save localStorage data:", err)
		}
	}

	// Save config data if provided
	if input.ConfigData != "" {
		configPath := filepath.Join(bugReportFolder, "config.json")
		if err := os.WriteFile(configPath, []byte(input.ConfigData), 0644); err != nil {
			Log("Failed to save config data:", err)
		}
	}

	// Create the report.txt file with user's description
	reportContent := fmt.Sprintf(`EMLy Bug Report
================

Name: %s
Email: %s

Description:
%s

Generated: %s
`, input.Name, input.Email, input.Description, time.Now().Format("2006-01-02 15:04:05"))

	reportPath := filepath.Join(bugReportFolder, "report.txt")
	if err := os.WriteFile(reportPath, []byte(reportContent), 0644); err != nil {
		return nil, fmt.Errorf("failed to save report file: %w", err)
	}

	// Get and save machine/system information
	machineInfo, err := utils.GetMachineInfo()
	if err == nil && machineInfo != nil {
		sysInfoContent := fmt.Sprintf(`System Information
==================

Hostname: %s
OS: %s
Version: %s
Hardware ID: %s
External IP: %s
`, machineInfo.Hostname, machineInfo.OS, machineInfo.Version, machineInfo.HWID, machineInfo.ExternalIP)

		sysInfoPath := filepath.Join(bugReportFolder, "system_info.txt")
		if err := os.WriteFile(sysInfoPath, []byte(sysInfoContent), 0644); err != nil {
			Log("Failed to save system info:", err)
		}
	}

	// Create zip archive of the folder
	zipPath := bugReportFolder + ".zip"
	if err := zipFolder(bugReportFolder, zipPath); err != nil {
		return nil, fmt.Errorf("failed to create zip file: %w", err)
	}

	result := &SubmitBugReportResult{
		ZipPath:    zipPath,
		FolderPath: bugReportFolder,
	}

	// Attempt to upload to the bug report API server (only if reachable)
	if !a.CheckBugReportAPI() {
		Log("Bug report API is offline, skipping upload")
		result.UploadError = "Bug report API is offline"
	} else {
		reportID, uploadErr := a.UploadBugReport(bugReportFolder, input)
		if uploadErr != nil {
			Log("Bug report upload failed (falling back to local zip):", uploadErr)
			result.UploadError = uploadErr.Error()
		} else {
			result.Uploaded = true
			result.ReportID = reportID
		}
	}

	return result, nil
}

// UploadBugReport uploads the bug report files from the temp folder to the
// configured API server. Returns the server-assigned report ID on success.
//
// Parameters:
//   - folderPath: Path to the bug report folder containing the files
//   - input: Original bug report input with user details
//
// Returns:
//   - int64: Server-assigned report ID
//   - error: Error if upload fails or API is not configured
func (a *App) UploadBugReport(folderPath string, input BugReportInput) (int64, error) {
	// Load config to get API URL and key
	cfgPath := utils.DefaultConfigPath()
	cfg, err := utils.LoadConfig(cfgPath)
	if err != nil {
		return 0, fmt.Errorf("failed to load config: %w", err)
	}

	apiURL := cfg.EMLy.BugReportAPIURL
	apiKey := cfg.EMLy.BugReportAPIKey

	if apiURL == "" {
		return 0, fmt.Errorf("bug report API URL not configured")
	}
	if apiKey == "" {
		return 0, fmt.Errorf("bug report API key not configured")
	}

	// Build multipart form
	var buf bytes.Buffer
	writer := multipart.NewWriter(&buf)

	// Add text fields
	writer.WriteField("name", input.Name)
	writer.WriteField("email", input.Email)
	writer.WriteField("description", input.Description)

	// Add machine identification fields
	machineInfo, err := utils.GetMachineInfo()
	if err == nil && machineInfo != nil {
		writer.WriteField("hwid", machineInfo.HWID)
		writer.WriteField("hostname", machineInfo.Hostname)

		// Add system_info as JSON string
		sysInfoJSON, jsonErr := json.Marshal(machineInfo)
		if jsonErr == nil {
			writer.WriteField("system_info", string(sysInfoJSON))
		}
	}

	// Add current OS username
	if currentUser, userErr := os.UserHomeDir(); userErr == nil {
		writer.WriteField("os_user", filepath.Base(currentUser))
	}

	// Add files from the folder
	fileRoles := map[string]string{
		"screenshot": "screenshot",
		"mail_file":  "mail_file",
		"localStorage.json": "localstorage",
		"config.json":       "config",
	}

	entries, _ := os.ReadDir(folderPath)
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		filename := entry.Name()

		// Determine file role
		var role string
		for pattern, r := range fileRoles {
			if filename == pattern {
				role = r
				break
			}
		}
		// Match screenshot and mail files by prefix/extension
		if role == "" {
			if filepath.Ext(filename) == ".png" {
				role = "screenshot"
			} else if filepath.Ext(filename) == ".eml" || filepath.Ext(filename) == ".msg" {
				role = "mail_file"
			}
		}
		if role == "" {
			continue // skip report.txt and system_info.txt (sent as fields)
		}

		filePath := filepath.Join(folderPath, filename)
		fileData, readErr := os.ReadFile(filePath)
		if readErr != nil {
			continue
		}

		part, partErr := writer.CreateFormFile(role, filename)
		if partErr != nil {
			continue
		}
		part.Write(fileData)
	}

	writer.Close()

	// Send HTTP request
	endpoint := apiURL + "/api/bug-reports"
	req, err := http.NewRequest("POST", endpoint, &buf)
	if err != nil {
		return 0, fmt.Errorf("failed to create request: %w", err)
	}
	req.Header.Set("Content-Type", writer.FormDataContentType())
	req.Header.Set("X-API-Key", apiKey)

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return 0, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)

	if resp.StatusCode != http.StatusCreated {
		return 0, fmt.Errorf("server returned status %d: %s", resp.StatusCode, string(body))
	}

	// Parse response
	var response struct {
		Success  bool  `json:"success"`
		ReportID int64 `json:"report_id"`
	}
	if err := json.Unmarshal(body, &response); err != nil {
		return 0, fmt.Errorf("failed to parse response: %w", err)
	}

	return response.ReportID, nil
}

// =============================================================================
// Helper Functions
// =============================================================================

// zipFolder creates a zip archive containing all files from the source folder.
// Directories are traversed recursively but stored implicitly (no directory entries).
//
// Parameters:
//   - sourceFolder: Path to the folder to zip
//   - destZip: Path where the zip file should be created
//
// Returns:
//   - error: Error if any file operation fails
func zipFolder(sourceFolder, destZip string) error {
	// Create the zip file
	zipFile, err := os.Create(destZip)
	if err != nil {
		return err
	}
	defer zipFile.Close()

	zipWriter := zip.NewWriter(zipFile)
	defer zipWriter.Close()

	// Walk through the folder and add all files
	return filepath.Walk(sourceFolder, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Skip the root folder itself
		if path == sourceFolder {
			return nil
		}

		// Get relative path for the zip entry
		relPath, err := filepath.Rel(sourceFolder, path)
		if err != nil {
			return err
		}

		// Skip directories (they're created implicitly)
		if info.IsDir() {
			return nil
		}

		// Create the file entry in the zip
		writer, err := zipWriter.Create(relPath)
		if err != nil {
			return err
		}

		// Read and write the file content
		fileContent, err := os.ReadFile(path)
		if err != nil {
			return err
		}

		_, err = writer.Write(fileContent)
		return err
	})
}
