package extension

import (
	"crypto/sha256"
	"fmt"
	"strings"
)

const (
	PluginManifestVersion = "1"
)

var (
	PluginPermissionStorage       PluginPermissionScope = "storage"        // Allows the plugin to store its own data
	PluginPermissionDatabase      PluginPermissionScope = "database"       // Allows the plugin to read non-auth data from the database and write to it
	PluginPermissionPlayback      PluginPermissionScope = "playback"       // Allows the plugin to use the playback manager
	PluginPermissionAnilist       PluginPermissionScope = "anilist"        // Allows the plugin to use the Anilist client
	PluginPermissionAnilistToken  PluginPermissionScope = "anilist-token"  // Allows the plugin to see and use the Anilist token
	PluginPermissionSystem        PluginPermissionScope = "system"         // Allows the plugin to use the OS/Filesystem/Filepath functions. SystemPermissions must be granted additionally.
	PluginPermissionCron          PluginPermissionScope = "cron"           // Allows the plugin to use the cron manager
	PluginPermissionNotification  PluginPermissionScope = "notification"   // Allows the plugin to use the notification manager
	PluginPermissionDiscord       PluginPermissionScope = "discord"        // Allows the plugin to use the discord rpc
	PluginPermissionTorrentClient PluginPermissionScope = "torrent-client" // Allows the plugin to use the torrent client
)

type PluginManifest struct {
	Version string `json:"version"`
	// Permissions is a list of permissions that the plugin is asking for.
	// The user must acknowledge these permissions before the plugin can be loaded.
	Permissions PluginPermissions `json:"permissions,omitempty"`
}

type PluginPermissions struct {
	Scopes []PluginPermissionScope `json:"scopes,omitempty"`
	Allow  PluginAllowlist         `json:"allow,omitempty"`
}

// PluginAllowlist is a list of system permissions that the plugin is asking for.
//
// The user must acknowledge these permissions before the plugin can be loaded.
type PluginAllowlist struct {
	// ReadPaths is a list of paths that the plugin is allowed to read from.
	ReadPaths []string `json:"readPaths,omitempty"`
	// WritePaths is a list of paths that the plugin is allowed to write to.
	WritePaths []string `json:"writePaths,omitempty"`
	// CommandScopes defines the commands that the plugin is allowed to execute.
	// Each command scope has a unique identifier and configuration.
	CommandScopes []CommandScope `json:"commandScopes,omitempty"`
}

// CommandScope defines a specific command or set of commands that can be executed
// with specific arguments and validation rules.
type CommandScope struct {
	// Description explains why this command scope is needed
	Description string `json:"description,omitempty"`
	// Command is the executable program
	Command string `json:"command"`
	// Args defines the allowed arguments for this command
	// If nil or empty, no arguments are allowed
	// If contains "$ARGS", any arguments are allowed at that position
	Args []CommandArg `json:"args,omitempty"`
}

// CommandArg represents an argument for a command
type CommandArg struct {
	// Value is the fixed argument value
	// If empty, Validator must be set
	Value string `json:"value,omitempty"`
	// Validator is a Perl compatible regex pattern to validate dynamic argument values
	// Special values:
	// - "$ARGS" allows any arguments at this position
	// - "$PATH" allows any valid file path
	Validator string `json:"validator,omitempty"`
}

// ReadAllowCommands returns a human-readable representation of the commands
// that the plugin is allowed to execute.
func (p *PluginAllowlist) ReadAllowCommands() []string {
	if p == nil {
		return []string{}
	}

	result := make([]string, 0)

	// Add commands from CommandScopes
	if len(p.CommandScopes) > 0 {
		for _, scope := range p.CommandScopes {
			cmd := scope.Command

			// Build argument string
			args := ""
			for i, arg := range scope.Args {
				if i > 0 {
					args += " "
				}

				if arg.Value != "" {
					args += arg.Value
				} else if arg.Validator == "$ARGS" {
					args += "[any arguments]"
				} else if arg.Validator == "$PATH" {
					args += "[any path]"
				} else if arg.Validator != "" {
					args += "[matching: " + arg.Validator + "]"
				}
			}

			if args != "" {
				cmd += " " + args
			}

			// Add description if available
			if scope.Description != "" {
				cmd += " - " + scope.Description
			}

			result = append(result, cmd)
		}
	}

	return result
}

func (p *PluginPermissions) GetHash() string {
	if p == nil {
		return ""
	}

	if len(p.Scopes) == 0 &&
		len(p.Allow.ReadPaths) == 0 &&
		len(p.Allow.WritePaths) == 0 &&
		len(p.Allow.CommandScopes) == 0 {
		return ""
	}

	h := sha256.New()

	// Hash scopes
	for _, scope := range p.Scopes {
		h.Write([]byte(scope))
	}

	// Hash allowlist read paths
	for _, path := range p.Allow.ReadPaths {
		h.Write([]byte("read:" + path))
	}

	// Hash allowlist write paths
	for _, path := range p.Allow.WritePaths {
		h.Write([]byte("write:" + path))
	}

	// Hash command scopes
	for _, cmd := range p.Allow.CommandScopes {
		h.Write([]byte("cmd:" + cmd.Command + ":" + cmd.Description))
		for _, arg := range cmd.Args {
			h.Write([]byte("arg:" + arg.Value + ":" + arg.Validator))
		}
	}

	return fmt.Sprintf("%x", h.Sum(nil))
}

func (p *PluginPermissions) GetDescription() string {
	if p == nil {
		return ""
	}

	// Check if any permissions exist
	if len(p.Scopes) == 0 &&
		len(p.Allow.ReadPaths) == 0 &&
		len(p.Allow.WritePaths) == 0 &&
		len(p.Allow.CommandScopes) == 0 {
		return "No permissions requested."
	}

	var desc strings.Builder

	// Add scopes section if any exist
	if len(p.Scopes) > 0 {
		desc.WriteString("Application:\n")
		for _, scope := range p.Scopes {
			desc.WriteString("• ")
			switch scope {
			case PluginPermissionStorage:
				desc.WriteString("Storage: Store plugin data locally\n")
			case PluginPermissionDatabase:
				desc.WriteString("Database: Read and write non-auth data\n")
			case PluginPermissionPlayback:
				desc.WriteString("Playback: Control media playback and media players\n")
			case PluginPermissionAnilist:
				desc.WriteString("AniList: View and edit your AniList lists\n")
			case PluginPermissionAnilistToken:
				desc.WriteString("AniList Token: View and use your AniList token\n")
			case PluginPermissionSystem:
				desc.WriteString("System: Access OS functions (accessing files, running commands, etc.)\n")
			case PluginPermissionCron:
				desc.WriteString("Cron: Schedule automated tasks\n")
			case PluginPermissionNotification:
				desc.WriteString("Notification: Send system notifications\n")
			case PluginPermissionDiscord:
				desc.WriteString("Discord: Set Discord Rich Presence\n")
			case PluginPermissionTorrentClient:
				desc.WriteString("Torrent Client: Control torrent clients\n")
			default:
				desc.WriteString(string(scope) + "\n")
			}
		}
		desc.WriteString("\n")
	}

	// Add file permissions if any exist
	hasFilePaths := len(p.Allow.ReadPaths) > 0 || len(p.Allow.WritePaths) > 0
	if hasFilePaths {
		desc.WriteString("File System:\n")

		if len(p.Allow.ReadPaths) > 0 {
			desc.WriteString("• Read from:\n")
			for _, path := range p.Allow.ReadPaths {
				desc.WriteString("\t  - " + explainPath(path) + "\n")
			}
		}

		if len(p.Allow.WritePaths) > 0 {
			desc.WriteString("• Write to:\n")
			for _, path := range p.Allow.WritePaths {
				desc.WriteString("\t  - " + explainPath(path) + "\n")
			}
		}
		desc.WriteString("\n")
	}

	// Add command permissions if any exist
	if len(p.Allow.CommandScopes) > 0 {
		desc.WriteString("Commands:\n")
		for _, cmd := range p.Allow.CommandScopes {
			cmdDesc := "• " + cmd.Command

			// Format arguments
			if len(cmd.Args) > 0 {
				argsDesc := ""
				for _, arg := range cmd.Args {
					if arg.Value != "" {
						argsDesc += " " + arg.Value
					} else if arg.Validator == "$ARGS" {
						argsDesc += " [any arguments]"
					} else if arg.Validator == "$PATH" {
						argsDesc += " [any file path]"
					} else if arg.Validator != "" {
						argsDesc += " [pattern: " + arg.Validator + "]"
					}
				}
				cmdDesc += argsDesc
			}

			// Add command description if available
			if cmd.Description != "" {
				cmdDesc += "\n\t  Purpose: " + cmd.Description
			}

			desc.WriteString(cmdDesc + "\n")
		}
	}

	return strings.TrimSpace(desc.String())
}

// explainPath adds human-readable descriptions to paths containing environment variables
func explainPath(path string) string {
	environmentVars := map[string]string{
		"$SEANIME_ANIME_LIBRARY": "Your anime library directories",
		"$HOME":                  "Your system's Home directory",
		"$CACHE":                 "Your system's Cache directory",
		"$TEMP":                  "Your system's Temporary directory",
		"$CONFIG":                "Your system's Config directory",
		"$DOWNLOAD":              "Your system's Downloads directory",
		"$DESKTOP":               "Your system's Desktop directory",
		"$DOCUMENT":              "Your system's Documents directory",
	}

	result := path

	// Check if we need to add an explanation
	needsExplanation := false
	explanation := ""

	for envVar, description := range environmentVars {
		if strings.Contains(path, envVar) {
			if explanation != "" {
				explanation += ", "
			}
			explanation += fmt.Sprintf("%s = %s", envVar, description)
			needsExplanation = true
		}
	}

	if needsExplanation {
		result += " (" + explanation + ")"
	}

	return result
}

////////////////////////////////////////////////////////////////////////////////////////////////////////

type PluginExtension interface {
	BaseExtension
	GetPermissionHash() string
}

type PluginExtensionImpl struct {
	ext *Extension
}

func NewPluginExtension(ext *Extension) PluginExtension {
	return &PluginExtensionImpl{
		ext: ext,
	}
}

func (m *PluginExtensionImpl) GetPermissionHash() string {
	if m.ext.Plugin == nil {
		return ""
	}

	return m.ext.Plugin.Permissions.GetHash()
}

func (m *PluginExtensionImpl) GetExtension() *Extension {
	return m.ext
}

func (m *PluginExtensionImpl) GetType() Type {
	return m.ext.Type
}

func (m *PluginExtensionImpl) GetID() string {
	return m.ext.ID
}

func (m *PluginExtensionImpl) GetName() string {
	return m.ext.Name
}

func (m *PluginExtensionImpl) GetVersion() string {
	return m.ext.Version
}

func (m *PluginExtensionImpl) GetManifestURI() string {
	return m.ext.ManifestURI
}

func (m *PluginExtensionImpl) GetLanguage() Language {
	return m.ext.Language
}

func (m *PluginExtensionImpl) GetLang() string {
	return GetExtensionLang(m.ext.Lang)
}

func (m *PluginExtensionImpl) GetDescription() string {
	return m.ext.Description
}

func (m *PluginExtensionImpl) GetAuthor() string {
	return m.ext.Author
}

func (m *PluginExtensionImpl) GetPayload() string {
	return m.ext.Payload
}

func (m *PluginExtensionImpl) GetWebsite() string {
	return m.ext.Website
}

func (m *PluginExtensionImpl) GetIcon() string {
	return m.ext.Icon
}

func (m *PluginExtensionImpl) GetPermissions() []string {
	return m.ext.Permissions
}

func (m *PluginExtensionImpl) GetUserConfig() *UserConfig {
	return m.ext.UserConfig
}

func (m *PluginExtensionImpl) GetSavedUserConfig() *SavedUserConfig {
	return m.ext.SavedUserConfig
}

func (m *PluginExtensionImpl) GetPayloadURI() string {
	return m.ext.PayloadURI
}

func (m *PluginExtensionImpl) GetIsDevelopment() bool {
	return m.ext.IsDevelopment
}
