package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"archivmail/config"
	"archivmail/internal/index"
	"archivmail/internal/storage"
	"archivmail/pkg/mailparser"
)


type importResult struct {
	Status     string `json:"status"`
	Imported   int    `json:"imported"`
	Skipped    int    `json:"skipped"`
	Errors     int    `json:"errors"`
	DurationSec float64 `json:"duration_sec"`
}

func runImport(args []string) {
	fs := flag.NewFlagSet("import", flag.ExitOnError)
	configPath := fs.String("config", "/etc/archivmail/config.yml", "path to config file")
	file      := fs.String("file", "", "single EML or MBOX file to import")
	dir       := fs.String("dir", "", "directory to import EML/MBOX files from")
	recursive := fs.Bool("recursive", false, "recurse into subdirectories (with --dir)")
	dryRun    := fs.Bool("dry-run", false, "simulate import without saving")
	jsonOut   := fs.Bool("json", false, "machine-readable JSON output")
	tenantFlag := fs.Int64("tenant", 0, "tenant ID to assign imported mails to (0 = none/global)")

	fs.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage: archivmail import [flags]")
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "Flags:")
		fs.PrintDefaults()
	}
	fs.Parse(args)

	if *file == "" && *dir == "" {
		fmt.Fprintln(os.Stderr, "error: --file or --dir required")
		fs.Usage()
		os.Exit(1)
	}

	start := time.Now()

	cfg, err := config.Load(*configPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: load config: %v\n", err)
		os.Exit(1)
	}

	storeCfg := storage.Config{
		Dir:             cfg.Storage.StorePath,
		Keyfile:         cfg.Storage.Keyfile,
		DSN:             cfg.Database.DSN(),
		CompressEnabled: cfg.Storage.Compress,
	}
	mailStore, err := storage.New(storeCfg)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: storage init: %v\n", err)
		os.Exit(1)
	}
	defer mailStore.Close()

	batchSize := cfg.Index.BatchSize
	if batchSize <= 0 {
		batchSize = 100
	}
	backend := cfg.Index.Backend
	if backend == "" {
		backend = "manticore"
	}

	var idxMgr index.TenantIndexer
	if backend == "manticore" {
		dsn := cfg.Index.ManticoreDSN
		if dsn == "" {
			dsn = "manticore@tcp(127.0.0.1:9306)/?charset=utf8mb4"
		}
		m, err := index.NewManticoreTenantManager(dsn)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: manticore init: %v\n", err)
			os.Exit(1)
		}
		idxMgr = m
	} else {
		m, err := index.NewTenantIndexManager(cfg.Index.Path, batchSize, backend)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: index init: %v\n", err)
			os.Exit(1)
		}
		idxMgr = m
	}
	defer idxMgr.Close()

	// Collect files to process
	type fileEntry struct {
		path   string
		isMbox bool
	}
	var files []fileEntry

	if *file != "" {
		isMbox := strings.HasSuffix(strings.ToLower(*file), ".mbox")
		files = append(files, fileEntry{*file, isMbox})
	}

	if *dir != "" {
		info, err := os.Stat(*dir)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: cannot access dir %s: %v\n", *dir, err)
			os.Exit(1)
		}
		if !info.IsDir() {
			fmt.Fprintf(os.Stderr, "error: %s is not a directory\n", *dir)
			os.Exit(1)
		}

		walkFn := func(path string, d os.DirEntry, werr error) error {
			if werr != nil {
				return werr
			}
			if d.IsDir() {
				if !*recursive && path != *dir {
					return filepath.SkipDir
				}
				return nil
			}
			lower := strings.ToLower(d.Name())
			if strings.HasSuffix(lower, ".eml") {
				files = append(files, fileEntry{path, false})
			} else if strings.HasSuffix(lower, ".mbox") {
				files = append(files, fileEntry{path, true})
			}
			return nil
		}
		filepath.WalkDir(*dir, walkFn)
	}

	if len(files) == 0 {
		if !*jsonOut {
			fmt.Println("No EML or MBOX files found.")
		} else {
			printImportJSON(importResult{Status: "done"}, start)
		}
		os.Exit(0)
	}

	if !*jsonOut {
		if *dryRun {
			fmt.Printf("Dry run – scanning %d file(s)...\n", len(files))
		} else {
			fmt.Printf("Found %d file(s) to process...\n", len(files))
		}
	}

	var tenantID *int64
	if *tenantFlag > 0 {
		tid := *tenantFlag
		tenantID = &tid
	}

	imported := 0
	skipped  := 0
	errors   := 0
	total    := 0

	for _, fe := range files {
		raw, err := os.ReadFile(fe.path)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: read %s: %v\n", fe.path, err)
			errors++
			continue
		}

		var messages [][]byte
		if fe.isMbox {
			messages = mailparser.SplitMbox(raw)
			if len(messages) == 0 {
				continue
			}
		} else {
			messages = [][]byte{raw}
		}

		for _, msg := range messages {
			total++
			result := importMessage(mailStore, idxMgr, msg, *dryRun, tenantID)
			switch result {
			case "imported":
				imported++
			case "skipped":
				skipped++
			case "error":
				errors++
			}
			if !*jsonOut && total%100 == 0 {
				fmt.Printf("Progress: %d processed (imported: %d, skipped: %d, errors: %d)\n",
					total, imported, skipped, errors)
			}
		}
	}

	if *jsonOut {
		printImportJSON(importResult{
			Status:      "done",
			Imported:    imported,
			Skipped:     skipped,
			Errors:      errors,
			DurationSec: time.Since(start).Seconds(),
		}, start)
	} else {
		fmt.Printf("\nFertig:\n")
		fmt.Printf("  Importiert:   %d\n", imported)
		fmt.Printf("  Übersprungen: %d (Duplikate)\n", skipped)
		fmt.Printf("  Fehler:       %d\n", errors)
		if *dryRun {
			fmt.Println("\n[dry-run] Keine Daten wurden gespeichert.")
		}
	}

	if errors > 0 {
		os.Exit(1)
	}
}

// importMessage stores and indexes a single raw message. Returns "imported", "skipped", or "error".
// tenantID, when non-nil, assigns the imported mail to that tenant (both in
// storage and in the tenant-specific search index) instead of the
// global/tenant-less context.
func importMessage(mailStore *storage.Store, idxMgr index.TenantIndexer, raw []byte, dryRun bool, tenantID *int64) string {
	pm, err := mailparser.Parse(raw)
	if err != nil {
		fmt.Fprintf(os.Stderr, "warning: parse failed: %v\n", err)
		return "error"
	}

	if dryRun {
		return "imported"
	}

	id, err := mailStore.Save(context.Background(), raw, pm.Date, tenantID)
	if err != nil {
		fmt.Fprintf(os.Stderr, "warning: save failed: %v\n", err)
		return "error"
	}

	// PROJ-52: mark bulk-imported mails as source 'import' for reconciliation.
	if err := mailStore.TagSource(context.Background(), id, "import", nil); err != nil {
		fmt.Fprintf(os.Stderr, "warning: tag source failed: %v\n", err)
	}

	var attachNames []string
	for _, a := range pm.Attachments {
		attachNames = append(attachNames, a.Filename)
	}

	doc := index.MailDocument{
		ID:            id,
		From:          pm.From,
		To:            strings.Join(pm.To, " "),
		CC:            strings.Join(pm.CC, " "),
		Subject:       pm.Subject,
		Body:          pm.TextBody + " " + pm.HTMLBody,
		AttachNames:   strings.Join(attachNames, " "),
		HasAttachment: len(pm.Attachments) > 0,
		Date:          pm.Date,
		Size:          int64(len(raw)),
		TenantID:      tenantID,
	}

	if err := idxMgr.ForTenant(tenantID).IndexSync(doc); err != nil {
		fmt.Fprintf(os.Stderr, "warning: index failed for %s: %v\n", id, err)
		return "error"
	}

	return "imported"
}

func printImportJSON(r importResult, start time.Time) {
	r.DurationSec = time.Since(start).Seconds()
	enc := json.NewEncoder(os.Stdout)
	enc.SetIndent("", "  ")
	enc.Encode(r)
}

func printHelp() {
	fmt.Printf(`archivmail %s – Mail-Archiv-Daemon und CLI

Commands:
  serve          Daemon starten (Standard wenn kein Befehl angegeben)
  import         E-Mails importieren (EML, MBOX, Verzeichnis)
  import-piler   Aus mailpiler migrieren (pilerexport oder direkte Store-Methode)
  export         E-Mails exportieren (EML, MBOX)
  reindex        Index neu aufbauen (alle oder pro Mandant)
  purge          Mails mit abgelaufener Aufbewahrungsfrist löschen (cron-fähig)
  recompress     Bestehende Mails nachträglich gzip-komprimieren
  rethread       Thread-IDs rückwirkend aus In-Reply-To/References befüllen
  ocr-reprocess  OCR für Anhänge nachholen (alle oder pro Mandant/Status)
  index-pending  Ungeindexte Mails nachindexieren (cron-fähig, PROJ-58 batch_mode)
  update         Auf neueste Version aktualisieren (führt update.sh aus)
  status         Healthcheck für DB, Manticore und Storage
  version        Version anzeigen
  help           Diese Hilfe anzeigen

archivmail import [flags]
  --config     Pfad zur Konfigurationsdatei (Standard: /etc/archivmail/config.yml)
  --file       Einzelne EML- oder MBOX-Datei
  --dir        Verzeichnis mit EML/MBOX-Dateien
  --recursive  Unterverzeichnisse einschließen (mit --dir)
  --tenant     Mandanten-ID für importierte Mails (0 = keine/global)
  --dry-run    Simulation ohne Speichern
  --json       Maschinenlesbare JSON-Ausgabe

archivmail import-piler [flags]
  --config       Pfad zur Konfigurationsdatei (Standard: /etc/archivmail/config.yml)
  --method       auto | pilerexport | direct (Standard: auto)
  --pilerexport  Pfad zum pilerexport Binary (auto-erkennung)
  --export-dir   Ausgabeverzeichnis für pilerexport (temp wenn leer)
  --store-dir    mailpiler Store-Verzeichnis (Standard: /var/piler/store)
  --key-file     mailpiler AES-Schlüsseldatei (Standard: /var/piler/store/piler.key)
  --date-from    Export ab Datum YYYY-MM-DD (pilerexport-Methode)
  --date-to      Export bis Datum YYYY-MM-DD (pilerexport-Methode)
  --dry-run      Simulation ohne Speichern
  --json         Maschinenlesbare JSON-Ausgabe

archivmail export [flags]
  --config     Pfad zur Konfigurationsdatei (Standard: /etc/archivmail/config.yml)
  --out        Zielverzeichnis oder Zieldatei (Pflicht)
  --format     eml (Standard) oder mbox
  --from       Filter nach Absender
  --to         Filter nach Empfänger
  --date-from  Filter ab Datum (ISO 8601: 2024-01-01)
  --date-to    Filter bis Datum (ISO 8601: 2024-12-31)
  --query      Volltext-Suche
  --force      Vorhandene Dateien überschreiben
  --json       Maschinenlesbare JSON-Ausgabe

archivmail reindex [flags]
  --config     Pfad zur Konfigurationsdatei (Standard: /etc/archivmail/config.yml)
  --tenant     Mandanten-ID für partiellen Reindex (0 = alle)

archivmail recompress [flags]
  --config     Pfad zur Konfigurationsdatei (Standard: /etc/archivmail/config.yml)
  --dry-run    Simulation: zeigt wie viel gespart würde, ohne Dateien zu ändern

archivmail rethread [flags]
  --config     Pfad zur Konfigurationsdatei (Standard: /etc/archivmail/config.yml)
  --dry-run    Simulation: zeigt wie viele Mails gethreaded würden, ohne DB zu ändern

archivmail status [flags]
  --config     Pfad zur Konfigurationsdatei (Standard: /etc/archivmail/config.yml)
  --json       Maschinenlesbare JSON-Ausgabe
`, AppVersion)
}
