package bootstrap

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"time"
)

type Config struct {
	Enabled              bool
	SourceType           string
	VSIXURL              string
	MarketplacePublisher string
	MarketplaceExtension string
	OutputDir            string
	BinaryPath           string
	AlwaysRefresh        bool
	ForceRefresh         bool
	HTTPTimeout          time.Duration
}

type Result struct {
	Enabled        bool   `json:"enabled"`
	Source         string `json:"source,omitempty"`
	Version        string `json:"version,omitempty"`
	URL            string `json:"url,omitempty"`
	BinaryPath     string `json:"binary_path,omitempty"`
	ReleaseDir     string `json:"release_dir,omitempty"`
	MarkerPath     string `json:"marker_path,omitempty"`
	Downloaded     bool   `json:"downloaded"`
	ReusedExisting bool   `json:"reused_existing"`
	Message        string `json:"message,omitempty"`
}

type marker struct {
	Source       string `json:"source"`
	URL          string `json:"url"`
	Version      string `json:"version"`
	DownloadedAt int64  `json:"downloaded_at"`
	NestedZip    string `json:"nested_zip"`
	Member       string `json:"member"`
	ReleaseRoot  string `json:"release_root"`
	Size         int    `json:"size"`
	Publisher    string `json:"publisher,omitempty"`
	Extension    string `json:"extension,omitempty"`
}

func Ensure(cfg Config) (Result, error) {
	result := Result{Enabled: cfg.Enabled}
	if !cfg.Enabled {
		result.Message = "bootstrap disabled"
		return result, nil
	}
	cfg = normalize(cfg)
	if cfg.BinaryPath == "" {
		return result, fmt.Errorf("bootstrap binary path is required")
	}
	if err := os.MkdirAll(filepath.Dir(cfg.BinaryPath), 0o755); err != nil {
		return result, err
	}
	if err := os.MkdirAll(cfg.OutputDir, 0o755); err != nil {
		return result, err
	}

	resolvedURL := strings.TrimSpace(cfg.VSIXURL)
	version := ""
	if cfg.SourceType == "marketplace" {
		url, ver, err := queryMarketplaceLatestVSIX(cfg)
		if err == nil {
			resolvedURL = url
			version = ver
		} else if resolvedURL == "" {
			return result, err
		}
	}
	if resolvedURL == "" {
		return result, fmt.Errorf("bootstrap VSIX URL is empty")
	}

	result.Source = cfg.SourceType
	result.URL = resolvedURL
	result.Version = version
	result.BinaryPath = cfg.BinaryPath
	result.MarkerPath = filepath.Join(filepath.Dir(cfg.BinaryPath), ".lingma-bootstrap.json")

	oldMarker := readMarker(result.MarkerPath)
	releaseDir := releaseDirForOutput(cfg.OutputDir, oldMarker.ReleaseRoot)
	result.ReleaseDir = releaseDir
	ready := hasRequiredAssets(releaseDir)

	if fileExists(cfg.BinaryPath) && ready && !cfg.ForceRefresh {
		if !cfg.AlwaysRefresh || (version != "" && oldMarker.Version == version) {
			if err := os.Chmod(cfg.BinaryPath, 0o755); err != nil {
				return result, err
			}
			result.ReusedExisting = true
			result.Message = "reused existing Lingma binary"
			return result, nil
		}
	}

	vsixBytes, err := downloadVSIX(resolvedURL, cfg.HTTPTimeout)
	if err != nil {
		if fileExists(cfg.BinaryPath) {
			result.ReusedExisting = true
			result.Message = fmt.Sprintf("download failed, reused existing Lingma: %v", err)
			return result, os.Chmod(cfg.BinaryPath, 0o755)
		}
		return result, err
	}
	result.Downloaded = true

	nestedName, nestedBytes, err := extractNestedZip(vsixBytes)
	if err != nil {
		return result, err
	}
	memberName, binaryBytes, releaseRoot, releaseFiles, err := extractRelease(nestedBytes)
	if err != nil {
		return result, err
	}
	releaseDir = releaseDirForOutput(cfg.OutputDir, releaseRoot)
	result.ReleaseDir = releaseDir
	if err := os.RemoveAll(releaseDir); err != nil {
		return result, err
	}
	if err := writeReleaseFiles(releaseDir, releaseRoot, releaseFiles); err != nil {
		return result, err
	}
	if err := os.WriteFile(cfg.BinaryPath, binaryBytes, 0o755); err != nil {
		return result, err
	}
	if !hasRequiredAssets(releaseDir) {
		return result, fmt.Errorf("extension assets missing after extraction under %s", releaseDir)
	}

	mk := marker{
		Source:       cfg.SourceType,
		URL:          resolvedURL,
		Version:      version,
		DownloadedAt: time.Now().Unix(),
		NestedZip:    nestedName,
		Member:       memberName,
		ReleaseRoot:  releaseRoot,
		Size:         len(binaryBytes),
		Publisher:    cfg.MarketplacePublisher,
		Extension:    cfg.MarketplaceExtension,
	}
	if err := writeMarker(result.MarkerPath, mk); err != nil {
		return result, err
	}
	result.Message = fmt.Sprintf("downloaded Lingma %s", versionOrUnknown(version))
	return result, nil
}

type marketplaceResponse struct {
	Results []struct {
		Extensions []struct {
			Versions []struct {
				Version string `json:"version"`
				Files   []struct {
					AssetType string `json:"assetType"`
					Source    string `json:"source"`
				} `json:"files"`
			} `json:"versions"`
		} `json:"extensions"`
	} `json:"results"`
}

type zipEntry struct {
	Name string
	Data []byte
	Mode os.FileMode
}

func normalize(cfg Config) Config {
	cfg.SourceType = strings.ToLower(strings.TrimSpace(cfg.SourceType))
	if cfg.SourceType == "" {
		cfg.SourceType = "marketplace"
	}
	if cfg.OutputDir == "" {
		cfg.OutputDir = filepath.Join(filepath.Dir(cfg.BinaryPath), "release")
	}
	if cfg.HTTPTimeout <= 0 {
		cfg.HTTPTimeout = 30 * time.Second
	}
	if strings.TrimSpace(cfg.MarketplacePublisher) == "" {
		cfg.MarketplacePublisher = "Alibaba-Cloud"
	}
	if strings.TrimSpace(cfg.MarketplaceExtension) == "" {
		cfg.MarketplaceExtension = "tongyi-lingma"
	}
	if strings.TrimSpace(cfg.VSIXURL) == "" {
		cfg.VSIXURL = "https://tongyi-code.oss-cn-hangzhou.aliyuncs.com/vscode/tongyi-lingma-latest.vsix"
	}
	return cfg
}

func queryMarketplaceLatestVSIX(cfg Config) (string, string, error) {
	payload := map[string]any{
		"filters": []any{map[string]any{
			"criteria": []any{
				map[string]any{"filterType": 7, "value": cfg.MarketplacePublisher + "." + cfg.MarketplaceExtension},
				map[string]any{"filterType": 8, "value": "Microsoft.VisualStudio.Code"},
			},
			"pageNumber": 1,
			"pageSize":   1,
			"sortBy":     0,
			"sortOrder":  0,
		}},
		"assetTypes": []any{},
		"flags":      950,
	}
	body, err := json.Marshal(payload)
	if err != nil {
		return "", "", err
	}
	req, err := http.NewRequest(http.MethodPost, "https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery", bytes.NewReader(body))
	if err != nil {
		return "", "", err
	}
	req.Header.Set("accept", "application/json;api-version=3.0-preview.1")
	req.Header.Set("content-type", "application/json")
	req.Header.Set("x-market-client-id", "VSCode 1.115.0")
	client := &http.Client{Timeout: cfg.HTTPTimeout}
	resp, err := client.Do(req)
	if err != nil {
		return "", "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return "", "", fmt.Errorf("marketplace query status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
	}
	var parsed marketplaceResponse
	if err := json.NewDecoder(resp.Body).Decode(&parsed); err != nil {
		return "", "", err
	}
	if len(parsed.Results) == 0 || len(parsed.Results[0].Extensions) == 0 || len(parsed.Results[0].Extensions[0].Versions) == 0 {
		return "", "", fmt.Errorf("no extension found from marketplace")
	}
	version := parsed.Results[0].Extensions[0].Versions[0].Version
	for _, file := range parsed.Results[0].Extensions[0].Versions[0].Files {
		if file.AssetType == "Microsoft.VisualStudio.Services.VSIXPackage" && strings.TrimSpace(file.Source) != "" {
			return file.Source, version, nil
		}
	}
	if version == "" {
		return "", "", fmt.Errorf("no version/vsix url found from marketplace")
	}
	fallback := fmt.Sprintf("https://marketplace.visualstudio.com/_apis/public/gallery/publishers/%s/vsextensions/%s/%s/vspackage", cfg.MarketplacePublisher, cfg.MarketplaceExtension, version)
	return fallback, version, nil
}

func downloadVSIX(url string, timeout time.Duration) ([]byte, error) {
	client := &http.Client{Timeout: timeout}
	resp, err := client.Get(url)
	if err != nil {
		return nil, fmt.Errorf("download VSIX: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return nil, fmt.Errorf("download VSIX status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
	}
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func extractNestedZip(vsix []byte) (string, []byte, error) {
	reader, err := zip.NewReader(bytes.NewReader(vsix), int64(len(vsix)))
	if err != nil {
		return "", nil, err
	}
	candidates := make([]*zip.File, 0)
	for _, file := range reader.File {
		name := file.Name
		if strings.HasPrefix(name, "extension/dist/bin/") && strings.HasSuffix(name, ".zip") && strings.Contains(name, "lingma-") {
			candidates = append(candidates, file)
		}
	}
	if len(candidates) == 0 {
		return "", nil, fmt.Errorf("no lingma-*.zip found in VSIX")
	}
	sort.Slice(candidates, func(i, j int) bool { return candidates[i].Name < candidates[j].Name })
	picked := candidates[len(candidates)-1]
	rc, err := picked.Open()
	if err != nil {
		return "", nil, err
	}
	defer rc.Close()
	data, err := io.ReadAll(rc)
	if err != nil {
		return "", nil, err
	}
	return picked.Name, data, nil
}

func extractRelease(inner []byte) (string, []byte, string, []zipEntry, error) {
	reader, err := zip.NewReader(bytes.NewReader(inner), int64(len(inner)))
	if err != nil {
		return "", nil, "", nil, err
	}
	binaryPath := pickLingmaBinaryPath(reader.File)
	if binaryPath == "" {
		return "", nil, "", nil, fmt.Errorf("Lingma binary not found inside nested zip")
	}
	releaseRoot := inferReleaseRoot(binaryPath)
	entries := make([]zipEntry, 0, len(reader.File))
	var binaryBytes []byte
	for _, file := range reader.File {
		if file.FileInfo().IsDir() {
			continue
		}
		if releaseRoot != "" && !strings.HasPrefix(file.Name, releaseRoot+"/") {
			continue
		}
		rc, err := file.Open()
		if err != nil {
			return "", nil, "", nil, err
		}
		data, err := io.ReadAll(rc)
		rc.Close()
		if err != nil {
			return "", nil, "", nil, err
		}
		entries = append(entries, zipEntry{Name: file.Name, Data: data, Mode: file.Mode()})
		if file.Name == binaryPath {
			binaryBytes = data
		}
	}
	if len(binaryBytes) == 0 {
		return "", nil, "", nil, fmt.Errorf("Lingma binary bytes missing from nested zip")
	}
	return binaryPath, binaryBytes, releaseRoot, entries, nil
}

func pickLingmaBinaryPath(files []*zip.File) string {
	preferredSuffix := platformBinarySuffix()
	for _, file := range files {
		if strings.HasSuffix(file.Name, preferredSuffix) {
			return file.Name
		}
	}
	for _, file := range files {
		if strings.HasSuffix(file.Name, "/Lingma") || file.Name == "Lingma" {
			return file.Name
		}
	}
	return ""
}

func platformBinarySuffix() string {
	if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" {
		return "x86_64_linux/Lingma"
	}
	if runtime.GOOS == "linux" && runtime.GOARCH == "arm64" {
		return "arm64_linux/Lingma"
	}
	if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" {
		return "arm64_darwin/Lingma"
	}
	if runtime.GOOS == "darwin" {
		return "x86_64_darwin/Lingma"
	}
	if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" {
		return "arm64_windows/Lingma"
	}
	if runtime.GOOS == "windows" {
		return "x86_64_windows/Lingma"
	}
	return "/Lingma"
}

func inferReleaseRoot(memberPath string) string {
	parts := strings.Split(memberPath, "/")
	platforms := []string{"x86_64_linux", "arm64_linux", "x86_64_darwin", "arm64_darwin", "x86_64_windows", "arm64_windows"}
	for _, platform := range platforms {
		for i, part := range parts {
			if part == platform && i > 0 {
				return strings.Join(parts[:i], "/")
			}
		}
	}
	if len(parts) > 1 {
		return parts[0]
	}
	return ""
}

func writeReleaseFiles(releaseDir string, releaseRoot string, files []zipEntry) error {
	prefix := ""
	if releaseRoot != "" {
		prefix = releaseRoot + "/"
	}
	for _, entry := range files {
		rel := entry.Name
		if prefix != "" {
			rel = strings.TrimPrefix(rel, prefix)
		}
		if rel == "" {
			continue
		}
		dest := filepath.Join(releaseDir, filepath.FromSlash(rel))
		if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
			return err
		}
		mode := entry.Mode
		if mode == 0 {
			mode = 0o644
		}
		if strings.HasSuffix(rel, "/Lingma") || filepath.Base(rel) == "Lingma" {
			mode = 0o755
		}
		if err := os.WriteFile(dest, entry.Data, mode); err != nil {
			return err
		}
	}
	return nil
}

func releaseDirForOutput(outputDir string, releaseRoot string) string {
	name := strings.TrimSpace(releaseRoot)
	if name == "" {
		return outputDir
	}
	return filepath.Join(outputDir, filepath.FromSlash(name))
}

func hasRequiredAssets(releaseDir string) bool {
	info, err := os.Stat(filepath.Join(releaseDir, "extension", "main.js"))
	return err == nil && !info.IsDir()
}

func readMarker(path string) marker {
	body, err := os.ReadFile(path)
	if err != nil {
		return marker{}
	}
	var mk marker
	if err := json.Unmarshal(body, &mk); err != nil {
		return marker{}
	}
	return mk
}

func writeMarker(path string, mk marker) error {
	body, err := json.MarshalIndent(mk, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(path, body, 0o644)
}

func fileExists(path string) bool {
	info, err := os.Stat(path)
	return err == nil && !info.IsDir()
}

func versionOrUnknown(version string) string {
	if strings.TrimSpace(version) == "" {
		return "unknown version"
	}
	return version
}
