package commands

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

	apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
	"github.com/modelcontextprotocol/registry/pkg/model"
)

func InitCommand() error {
	// Check if server.json already exists
	if _, err := os.Stat("server.json"); err == nil {
		return errors.New("server.json already exists")
	}

	// Detect if we're in a subdirectory of the git repository
	subfolder := detectSubfolder()

	// Try to detect values from environment
	name := detectServerName(subfolder)
	description := detectDescription()
	version := getVersionFromPackageJSON()
	if version == "" {
		version = "1.0.0"
	}
	repoURL := detectRepoURL()
	repoSource := MethodGitHub
	if repoURL != "" && !strings.Contains(repoURL, "github.com") {
		if strings.Contains(repoURL, "gitlab.com") {
			repoSource = "gitlab"
		} else {
			repoSource = "git"
		}
	}

	packageType := detectPackageType()
	packageIdentifier := detectPackageIdentifier(name, packageType)

	// Create example environment variables
	envVars := []model.KeyValueInput{
		{
			Name: "YOUR_API_KEY",
			InputWithVariables: model.InputWithVariables{
				Input: model.Input{
					Description: "Your API key for the service",
					IsRequired:  true,
					IsSecret:    true,
					Format:      model.FormatString,
				},
			},
		},
	}

	// Create the server structure
	server := createServerJSON(
		model.CurrentSchemaURL, name, description, version, repoURL, repoSource, subfolder,
		packageType, packageIdentifier, version, envVars,
	)

	// Write to file
	jsonData, err := json.MarshalIndent(server, "", "  ")
	if err != nil {
		return fmt.Errorf("error marshaling JSON: %w", err)
	}

	err = os.WriteFile("server.json", jsonData, 0600)
	if err != nil {
		return fmt.Errorf("error writing file: %w", err)
	}

	_, _ = fmt.Fprintln(os.Stdout, "Created server.json")
	_, _ = fmt.Fprintln(os.Stdout, "\nEdit server.json to update:")
	_, _ = fmt.Fprintln(os.Stdout, "  • Server name and description")
	_, _ = fmt.Fprintln(os.Stdout, "  • Package details")
	_, _ = fmt.Fprintln(os.Stdout, "  • Environment variables")
	_, _ = fmt.Fprintln(os.Stdout, "\nThen publish with:")
	_, _ = fmt.Fprintln(os.Stdout, "  mcp-publisher login github  # or your preferred auth method")
	_, _ = fmt.Fprintln(os.Stdout, "  mcp-publisher publish")

	return nil
}

func detectSubfolder() string {
	// Get current working directory
	cwd, err := os.Getwd()
	if err != nil {
		return ""
	}

	// Find git repository root
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	cmd := exec.CommandContext(ctx, "git", "rev-parse", "--show-toplevel")
	cmd.Dir = cwd
	output, err := cmd.Output()
	if err != nil {
		// Not in a git repository
		return ""
	}

	gitRoot := strings.TrimSpace(string(output))

	// Clean the paths to ensure proper comparison
	gitRoot = filepath.Clean(gitRoot)
	cwd = filepath.Clean(cwd)

	// If we're in the root, no subfolder
	if gitRoot == cwd {
		return ""
	}

	// Check if cwd is actually within gitRoot
	if !strings.HasPrefix(cwd, gitRoot) {
		return ""
	}

	// Calculate relative path from git root to current directory
	relPath, err := filepath.Rel(gitRoot, cwd)
	if err != nil {
		return ""
	}

	// Convert to forward slashes for consistency (important for cross-platform)
	return filepath.ToSlash(relPath)
}

// getMcpNameFromPackageJSON returns the `mcpName` field from package.json, or
// "" if not set. mcpName is the authoritative MCP server name when present —
// see docs/modelcontextprotocol-io/quickstart.mdx.
func getMcpNameFromPackageJSON() string {
	data, err := os.ReadFile("package.json")
	if err != nil {
		return ""
	}

	var pkg map[string]any
	if err := json.Unmarshal(data, &pkg); err != nil {
		return ""
	}

	if mcpName, ok := pkg["mcpName"].(string); ok && mcpName != "" {
		return mcpName
	}
	return ""
}

func getNameFromPackageJSON() string {
	data, err := os.ReadFile("package.json")
	if err != nil {
		return ""
	}

	var pkg map[string]any
	if err := json.Unmarshal(data, &pkg); err != nil {
		return ""
	}

	name, ok := pkg["name"].(string)
	if !ok || name == "" {
		return ""
	}

	// Convert npm package name to MCP server name
	// @org/package -> io.github.org/package
	if strings.HasPrefix(name, "@") {
		parts := strings.Split(name[1:], "/")
		if len(parts) == 2 {
			return fmt.Sprintf("io.github.%s/%s", parts[0], parts[1])
		}
	}
	return fmt.Sprintf("io.github.<your-username>/%s", name)
}

func getVersionFromPackageJSON() string {
	data, err := os.ReadFile("package.json")
	if err != nil {
		return ""
	}

	var pkg map[string]any
	if err := json.Unmarshal(data, &pkg); err != nil {
		return ""
	}

	version, ok := pkg["version"].(string)
	if !ok || version == "" {
		return ""
	}

	return version
}

func detectServerName(subfolder string) string {
	// mcpName in package.json is the authoritative server name when set, so it
	// takes precedence over names inferred from the git remote or the npm
	// `name` field.
	if name := getMcpNameFromPackageJSON(); name != "" {
		return name
	}

	// Try to get from git remote
	repoURL := detectRepoURL()
	if repoURL != "" && strings.Contains(repoURL, "github.com") {
		name := buildGitHubServerName(repoURL, subfolder)
		if name != "" {
			return name
		}
	}

	// Try to get from package.json
	name := getNameFromPackageJSON()
	if name != "" {
		return name
	}

	// Use current directory name as fallback
	if cwd, err := os.Getwd(); err == nil {
		return fmt.Sprintf("com.example/%s", filepath.Base(cwd))
	}

	return "com.example/my-mcp-server"
}

func buildGitHubServerName(repoURL, subfolder string) string {
	parts := strings.Split(repoURL, "/")
	if len(parts) < 5 {
		return ""
	}

	owner := parts[3]
	repo := strings.TrimSuffix(parts[4], ".git")

	// If we're in a subdirectory, use the current folder name
	if subfolder != "" {
		folderName := filepath.Base(subfolder)
		return fmt.Sprintf("io.github.%s/%s", owner, folderName)
	}

	return fmt.Sprintf("io.github.%s/%s", owner, repo)
}

func detectDescription() string {
	// Try to get from package.json
	if data, err := os.ReadFile("package.json"); err == nil {
		var pkg map[string]any
		if json.Unmarshal(data, &pkg) == nil {
			if desc, ok := pkg["description"].(string); ok && desc != "" {
				return desc
			}
		}
	}

	return "An MCP server that provides [describe what your server does]"
}

func detectRepoURL() string {
	sanitizeURL := func(url string) string {
		return strings.TrimPrefix(url, "git+")
	}

	// Try git remote
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	cmd := exec.CommandContext(ctx, "git", "remote", "get-url", "origin")
	if output, err := cmd.Output(); err == nil {
		url := strings.TrimSpace(string(output))
		// Convert SSH URL to HTTPS if needed
		if strings.HasPrefix(url, "git@github.com:") {
			url = strings.Replace(url, "git@github.com:", "https://github.com/", 1)
		}
		url = strings.TrimSuffix(url, ".git")
		return url
	}

	// Try package.json repository field
	if data, err := os.ReadFile("package.json"); err == nil {
		var pkg map[string]any
		if json.Unmarshal(data, &pkg) == nil {
			if repo, ok := pkg["repository"].(map[string]any); ok {
				if url, ok := repo["url"].(string); ok {
					return sanitizeURL(strings.TrimSuffix(url, ".git"))
				}
			}
			if repo, ok := pkg["repository"].(string); ok {
				return sanitizeURL(strings.TrimSuffix(repo, ".git"))
			}
		}
	}

	return ""
}

func detectPackageType() string {
	// Check for package.json
	if _, err := os.Stat("package.json"); err == nil {
		return model.RegistryTypeNPM
	}

	// Check for pyproject.toml or setup.py
	if _, err := os.Stat("pyproject.toml"); err == nil {
		return model.RegistryTypePyPI
	}
	if _, err := os.Stat("setup.py"); err == nil {
		return model.RegistryTypePyPI
	}

	// Check for Dockerfile
	if _, err := os.Stat("Dockerfile"); err == nil {
		return model.RegistryTypeOCI
	}

	// Default to npm as most common
	return model.RegistryTypeNPM
}

func detectPackageIdentifier(serverName string, packageType string) string {
	switch packageType {
	case model.RegistryTypeNPM:
		// Try to get from package.json
		if data, err := os.ReadFile("package.json"); err == nil {
			var pkg map[string]any
			if json.Unmarshal(data, &pkg) == nil {
				if name, ok := pkg["name"].(string); ok && name != "" {
					return name
				}
			}
		}
		// Convert server name to npm package name
		if strings.HasPrefix(serverName, "io.github.") {
			parts := strings.Split(serverName, "/")
			if len(parts) == 2 {
				owner := strings.TrimPrefix(parts[0], "io.github.")
				return fmt.Sprintf("@%s/%s", owner, parts[1])
			}
		}
		return "@your-org/your-package"

	case model.RegistryTypePyPI:
		// Try to get from pyproject.toml or setup.py
		if data, err := os.ReadFile("pyproject.toml"); err == nil {
			// Simple extraction - could be improved with proper TOML parser
			lines := strings.Split(string(data), "\n")
			for _, line := range lines {
				if strings.HasPrefix(line, "name") && strings.Contains(line, "=") {
					parts := strings.Split(line, "=")
					if len(parts) >= 2 {
						name := strings.Trim(parts[1], " \"'")
						if name != "" {
							return name
						}
					}
				}
			}
		}
		return "your-package"

	case model.RegistryTypeOCI:
		// Use a sensible default
		if strings.Contains(serverName, "/") {
			parts := strings.Split(serverName, "/")
			return parts[len(parts)-1]
		}
		return "your-image"

	default:
		return "your-package"
	}
}

func createServerJSON(
	currentSchema, name, description, version, repoURL, repoSource, subfolder,
	packageType, packageIdentifier, packageVersion string,
	envVars []model.KeyValueInput,
) apiv0.ServerJSON {
	// Create package based on type
	var pkg model.Package

	switch packageType {
	case model.RegistryTypeNPM:
		pkg = model.Package{
			RegistryType:         model.RegistryTypeNPM,
			Identifier:           packageIdentifier,
			Version:              packageVersion,
			EnvironmentVariables: envVars,
			Transport: model.Transport{
				Type: model.TransportTypeStdio,
			},
		}
	case model.RegistryTypePyPI:
		pkg = model.Package{
			RegistryType:         model.RegistryTypePyPI,
			Identifier:           packageIdentifier,
			Version:              packageVersion,
			EnvironmentVariables: envVars,
			Transport: model.Transport{
				Type: model.TransportTypeStdio,
			},
		}
	case model.RegistryTypeOCI:
		// OCI packages use canonical references: registry/namespace/image:tag
		// Format: docker.io/username/image:version
		canonicalRef := fmt.Sprintf("docker.io/%s:%s", packageIdentifier, packageVersion)
		pkg = model.Package{
			RegistryType: model.RegistryTypeOCI,
			Identifier:   canonicalRef,
			// No Version field for OCI - it's embedded in the canonical reference
			EnvironmentVariables: envVars,
			Transport: model.Transport{
				Type: model.TransportTypeStdio,
			},
		}
	case "url":
		pkg = model.Package{
			RegistryType:         "url",
			Identifier:           packageIdentifier,
			Version:              packageVersion,
			EnvironmentVariables: envVars,
			Transport: model.Transport{
				Type: model.TransportTypeStdio,
			},
		}
	default:
		pkg = model.Package{
			RegistryType:         packageType,
			Identifier:           packageIdentifier,
			Version:              packageVersion,
			EnvironmentVariables: envVars,
			Transport: model.Transport{
				Type: model.TransportTypeStdio,
			},
		}
	}

	// Create repository with optional subfolder
	var repo *model.Repository
	if repoURL != "" && repoSource != "" {
		repo = &model.Repository{
			URL:    repoURL,
			Source: repoSource,
		}

		// Only set subfolder if we're actually in a subdirectory
		if subfolder != "" {
			repo.Subfolder = subfolder
		}
	}

	// Create server structure
	return apiv0.ServerJSON{
		Schema:      currentSchema,
		Name:        name,
		Description: description,
		Repository:  repo,
		Version:     version,
		Packages:    []model.Package{pkg},
	}
}
