package main

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

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

type exportResult struct {
	Status      string  `json:"status"`
	Exported    int     `json:"exported"`
	Errors      int     `json:"errors"`
	Format      string  `json:"format"`
	Out         string  `json:"out"`
	DurationSec float64 `json:"duration_sec"`
}

func runExport(args []string) {
	fs := flag.NewFlagSet("export", flag.ExitOnError)
	configPath := fs.String("config", "/etc/archivmail/config.yml", "path to config file")
	out        := fs.String("out", "", "output directory (EML) or file path (MBOX) – required")
	format     := fs.String("format", "eml", "export format: eml or mbox")
	from       := fs.String("from", "", "filter by sender address")
	to         := fs.String("to", "", "filter by recipient address")
	dateFrom   := fs.String("date-from", "", "filter from date (ISO 8601: 2024-01-01)")
	dateTo     := fs.String("date-to", "", "filter to date (ISO 8601: 2024-12-31)")
	query      := fs.String("query", "", "fulltext search query")
	tenant     := fs.String("tenant", "", "export ALL mails of the given tenant id (mutually exclusive with from/to/date/query)")
	force      := fs.Bool("force", false, "overwrite existing output file")
	jsonOut    := fs.Bool("json", false, "machine-readable JSON output")

	fs.Usage = func() {
		fmt.Fprintln(os.Stderr, "Usage: archivmail export [flags]")
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "Exports archived mails either by search filters (from/to/date/query)")
		fmt.Fprintln(os.Stderr, "or, with --tenant, the complete mail set of a single tenant.")
		fmt.Fprintln(os.Stderr, "")
		fmt.Fprintln(os.Stderr, "Flags:")
		fs.PrintDefaults()
	}
	fs.Parse(args)

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

	// --tenant and search filters are mutually exclusive.
	var tenantID *int64
	if *tenant != "" {
		if *from != "" || *to != "" || *dateFrom != "" || *dateTo != "" || *query != "" {
			fmt.Fprintln(os.Stderr, "error: --tenant cannot be combined with --from/--to/--date-from/--date-to/--query")
			fs.Usage()
			os.Exit(1)
		}
		id, perr := strconv.ParseInt(*tenant, 10, 64)
		if perr != nil || id <= 0 {
			fmt.Fprintf(os.Stderr, "error: invalid --tenant %q (expected positive integer id)\n", *tenant)
			os.Exit(1)
		}
		tenantID = &id
	}

	if *format != "eml" && *format != "mbox" {
		fmt.Fprintf(os.Stderr, "error: unknown format %q (supported: eml, mbox)\n", *format)
		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"
	}
	// The fulltext index is only needed when exporting via search filters.
	var idx index.Indexer
	req := index.SearchRequest{
		Query:    *query,
		From:     *from,
		To:       *to,
		PageSize: 500,
	}
	if tenantID == nil {
		idx, err = index.New(cfg.Index.Path, batchSize, backend)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: index init: %v\n", err)
			os.Exit(1)
		}
		defer idx.Close()

		if *dateFrom != "" {
			if t, err := time.Parse(time.DateOnly, *dateFrom); err == nil {
				req.DateFrom = &t
			} else {
				fmt.Fprintf(os.Stderr, "error: invalid --date-from %q (expected YYYY-MM-DD)\n", *dateFrom)
				os.Exit(1)
			}
		}
		if *dateTo != "" {
			if t, err := time.Parse(time.DateOnly, *dateTo); err == nil {
				t = t.Add(24*time.Hour - time.Second)
				req.DateTo = &t
			} else {
				fmt.Fprintf(os.Stderr, "error: invalid --date-to %q (expected YYYY-MM-DD)\n", *dateTo)
				os.Exit(1)
			}
		}
	}

	// Prepare output
	var mboxFile *os.File

	if *format == "mbox" {
		outPath := *out
		// If out is a directory, write export.mbox inside it
		if info, err := os.Stat(outPath); err == nil && info.IsDir() {
			outPath = filepath.Join(outPath, "export.mbox")
		}
		if _, err := os.Stat(outPath); err == nil && !*force {
			fmt.Fprintf(os.Stderr, "error: %s already exists – use --force to overwrite\n", outPath)
			os.Exit(1)
		}
		mboxFile, err = os.OpenFile(outPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: create mbox file: %v\n", err)
			os.Exit(1)
		}
		defer mboxFile.Close()
		*out = outPath
	} else {
		if err := os.MkdirAll(*out, 0o755); err != nil {
			fmt.Fprintf(os.Stderr, "error: create output dir: %v\n", err)
			os.Exit(1)
		}
	}

	exported := 0
	errors   := 0

	// exportID loads a single mail and writes it in the selected format.
	// Returns false on a (logged) per-mail error so the caller can count it.
	exportID := func(id string) bool {
		raw, err := mailStore.Load(id)
		if err != nil {
			fmt.Fprintf(os.Stderr, "warning: load %s: %v\n", id, err)
			return false
		}
		if *format == "mbox" {
			if err := writeMboxMessage(mboxFile, raw); err != nil {
				fmt.Fprintf(os.Stderr, "warning: write mbox: %v\n", err)
				return false
			}
		} else {
			outPath := filepath.Join(*out, id+".eml")
			if _, err := os.Stat(outPath); err == nil && !*force {
				fmt.Fprintf(os.Stderr, "warning: %s exists, skipping (use --force)\n", outPath)
				return false
			}
			if err := os.WriteFile(outPath, raw, 0o644); err != nil {
				fmt.Fprintf(os.Stderr, "warning: write %s: %v\n", outPath, err)
				return false
			}
		}
		return true
	}

	if tenantID != nil {
		// Full tenant export: pull all mail IDs directly from storage.
		ids, err := mailStore.GetAllIDsByTenant(context.Background(), tenantID)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: get tenant ids: %v\n", err)
			os.Exit(1)
		}
		if len(ids) == 0 {
			if !*jsonOut {
				fmt.Printf("Hinweis: 0 Mails exportiert (Tenant %d hat keine Mails oder existiert nicht)\n", *tenantID)
			}
		}
		for _, id := range ids {
			if exportID(id) {
				exported++
			} else {
				errors++
			}
		}
		if !*jsonOut && len(ids) > 0 {
			fmt.Printf("Progress: %d exported, %d errors\n", exported, errors)
		}
	} else {
		page := 0
		for {
			req.Page = page
			result, err := idx.Search(req)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error: search failed: %v\n", err)
				os.Exit(1)
			}
			if len(result.Hits) == 0 {
				break
			}

			for _, hit := range result.Hits {
				if exportID(hit.ID) {
					exported++
				} else {
					errors++
				}
			}

			if !*jsonOut {
				fmt.Printf("Progress: %d exported, %d errors\n", exported, errors)
			}

			if exported+errors >= result.Total {
				break
			}
			page++
		}
	}

	if *jsonOut {
		r := exportResult{
			Status:      "done",
			Exported:    exported,
			Errors:      errors,
			Format:      *format,
			Out:         *out,
			DurationSec: time.Since(start).Seconds(),
		}
		enc := json.NewEncoder(os.Stdout)
		enc.SetIndent("", "  ")
		enc.Encode(r)
	} else {
		fmt.Printf("\nFertig:\n")
		fmt.Printf("  Exportiert: %d %s-Dateien\n", exported, strings.ToUpper(*format))
		fmt.Printf("  Fehler:     %d\n", errors)
		fmt.Printf("  Ziel:       %s\n", *out)
	}

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

// writeMboxMessage appends a single message to an open mbox file.
func writeMboxMessage(f *os.File, raw []byte) error {
	pm, err := mailparser.Parse(raw)
	sender := "unknown@archivmail"
	date   := time.Now()
	if err == nil {
		if pm.From != "" {
			sender = pm.From
		}
		if !pm.Date.IsZero() {
			date = pm.Date
		}
	}

	// mbox "From " separator line
	fmt.Fprintf(f, "From %s %s\n", sender, date.UTC().Format("Mon Jan _2 15:04:05 2006"))

	// Escape any "From " lines inside the message body
	lines := strings.Split(string(raw), "\n")
	for _, line := range lines {
		if strings.HasPrefix(line, "From ") {
			f.WriteString(">" + line + "\n")
		} else {
			f.WriteString(line + "\n")
		}
	}
	f.WriteString("\n")
	return nil
}
