package extractor

import (
	"archive/tar"
	"compress/gzip"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"gitea.loveuer.com/yizhisec/pkg3/logger"
)

type Options struct {
	OnProgress func(filename string, index int, total int)
	IsGzipped  *bool
}

type Option func(*Options)

func WithProgress(callback func(filename string, index int, total int)) Option {
	return func(o *Options) {
		o.OnProgress = callback
	}
}

func WithGzipCompression(isGzipped bool) Option {
	return func(o *Options) {
		o.IsGzipped = &isGzipped
	}
}

func Extract(ctx context.Context, src, destDir string, opts ...Option) error {
	options := &Options{}
	for _, opt := range opts {
		opt(options)
	}

	logger.Debug("开始解压: %s -> %s", src, destDir)

	f, err := os.Open(src)
	if err != nil {
		logger.Debug("打开源文件失败 %s: %v", src, err)
		return fmt.Errorf("failed to open source file: %w", err)
	}
	defer f.Close()

	var reader io.Reader = f
	var isGzipped bool

	if options.IsGzipped != nil {
		isGzipped = *options.IsGzipped
	} else {
		isGzipped = strings.HasSuffix(strings.ToLower(src), ".tar.gz") || strings.HasSuffix(strings.ToLower(src), ".tgz")
	}

	if isGzipped {
		gzReader, err := gzip.NewReader(f)
		if err != nil {
			logger.Debug("创建 gzip reader 失败: %v", err)
			return fmt.Errorf("failed to create gzip reader: %w", err)
		}
		defer gzReader.Close()
		reader = gzReader
	}

	if err := extractTar(ctx, reader, destDir, options); err != nil {
		return err
	}

	logger.Debug("解压完成: %s", src)
	return nil
}

func extractTar(ctx context.Context, r io.Reader, destDir string, options *Options) error {
	tarReader := tar.NewReader(r)
	fileCount := 0
	totalFiles := 0

	for {
		if ctx.Err() != nil {
			return ctx.Err()
		}

		header, err := tarReader.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			logger.Debug("读取 tar 条目失败: %v", err)
			return fmt.Errorf("failed to read tar header: %w", err)
		}

		target := filepath.Join(destDir, header.Name)
		logger.Debug("解压文件: %s", header.Name)

		if options.OnProgress != nil {
			options.OnProgress(header.Name, fileCount, totalFiles)
		}

		switch header.Typeflag {
		case tar.TypeDir:
			if err := os.MkdirAll(target, os.FileMode(header.Mode)); err != nil {
				logger.Debug("创建目录失败 %s: %v", target, err)
				return fmt.Errorf("failed to create directory %s: %w", target, err)
			}
		case tar.TypeReg:
			if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
				logger.Debug("创建父目录失败 %s: %v", filepath.Dir(target), err)
				return fmt.Errorf("failed to create parent directory: %w", err)
			}
			outFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode))
			if err != nil {
				logger.Debug("创建文件失败 %s: %v", target, err)
				return fmt.Errorf("failed to create file %s: %w", target, err)
			}
			if _, err := io.Copy(outFile, tarReader); err != nil {
				outFile.Close()
				logger.Debug("写入文件失败 %s: %v", target, err)
				return fmt.Errorf("failed to write file %s: %w", target, err)
			}
			outFile.Close()
			fileCount++
		case tar.TypeSymlink:
			if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
				logger.Debug("创建符号链接父目录失败 %s: %v", filepath.Dir(target), err)
				return fmt.Errorf("failed to create parent directory for symlink: %w", err)
			}
			os.Remove(target)
			if err := os.Symlink(header.Linkname, target); err != nil {
				logger.Debug("创建符号链接失败 %s -> %s: %v", target, header.Linkname, err)
				return fmt.Errorf("failed to create symlink %s -> %s: %w", target, header.Linkname, err)
			}
			fileCount++
		default:
			logger.Debug("跳过不支持的文件类型: %s (type: %v)", header.Name, header.Typeflag)
		}
	}

	logger.Debug("解压完成，共 %d 个文件", fileCount)
	return nil
}
