//go:generate go run .
package main

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"text/template"

	"github.com/uozi-tech/cosy/logger"
)

// NotifierInfo Structure to hold extracted notifier information
type NotifierInfo struct {
	Name      string
	Fields    []FieldInfo
	FileName  string
	ConfigKey string
}

// FieldInfo Structure to hold field information for notifier
type FieldInfo struct {
	Name  string
	Key   string
	Title string
}

// Template for the TypeScript config file
const tsConfigTemplate = `// This file is auto-generated by notification generator. DO NOT EDIT.
import type { ExternalNotifyConfig } from './types'

const {{.Name | replaceSpaces}}Config: ExternalNotifyConfig = {
  name: () => $gettext('{{.Name}}'),
  config: [
    {{- range .Fields}}
    {
      key: '{{.Key}}',
      label: '{{.Title}}',
    },
    {{- end}}
  ],
}

export default {{.Name | replaceSpaces}}Config
`

// Regular expression to extract @external_notifier annotation
var externalNotifierRegex = regexp.MustCompile(`@external_notifier\(([a-zA-Z0-9 _]+)\)`)

func main() {
	logger.Init("release")

	_, file, _, ok := runtime.Caller(0)
	if !ok {
		logger.Error("Unable to get the current file")
		return
	}
	basePath := filepath.Join(filepath.Dir(file), "../../")
	if err := GenerateExternalNotifiers(basePath); err != nil {
		fmt.Printf("error generating external notifier configs: %v\n", err)
	}
}

// GenerateExternalNotifiers generates TypeScript config files for external notifiers
func GenerateExternalNotifiers(root string) error {
	fmt.Println("Generating external notifier configs...")

	// Notification package path
	notificationPkgPath := filepath.Join(root, "internal/notification")
	outputDir := filepath.Join(root, "app/src/views/preference/components/ExternalNotify")

	// Create output directory if it doesn't exist
	if err := os.MkdirAll(outputDir, 0755); err != nil {
		return fmt.Errorf("error creating output directory: %w", err)
	}

	// Get all Go files in the notification package
	files, err := filepath.Glob(filepath.Join(notificationPkgPath, "*.go"))
	if err != nil {
		return fmt.Errorf("error scanning notification package: %w", err)
	}

	// Collect all notifier info
	var notifiers []NotifierInfo

	for _, file := range files {
		notifier, found := extractNotifierInfo(file)
		if found {
			notifiers = append(notifiers, notifier)
			logger.Infof("Found notifier: %s in %s\n", notifier.Name, file)
		}
	}

	// Generate TypeScript config files
	for _, notifier := range notifiers {
		if err := generateTSConfig(notifier, outputDir); err != nil {
			return fmt.Errorf("error generating config for %s: %w", notifier.Name, err)
		}
	}

	// Update index.ts
	if err := updateIndexFile(notifiers, outputDir); err != nil {
		return fmt.Errorf("error updating index.ts: %w", err)
	}

	logger.Info("Generation completed successfully!")
	return nil
}

// Extract notifier information from a Go file
func extractNotifierInfo(filePath string) (NotifierInfo, bool) {
	// Create the FileSet
	fset := token.NewFileSet()

	// Parse the file
	file, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
	if err != nil {
		logger.Errorf("Error parsing file %s: %v\n", filePath, err)
		return NotifierInfo{}, false
	}

	var notifierInfo NotifierInfo
	found := false

	// Look for the type declaration with the @external_notifier annotation
	for _, decl := range file.Decls {
		genDecl, ok := decl.(*ast.GenDecl)
		if !ok || genDecl.Tok != token.TYPE {
			continue
		}

		for _, spec := range genDecl.Specs {
			typeSpec, ok := spec.(*ast.TypeSpec)
			if !ok {
				continue
			}

			structType, ok := typeSpec.Type.(*ast.StructType)
			if !ok {
				continue
			}

			// Check if we have a comment with @external_notifier
			if genDecl.Doc != nil {
				for _, comment := range genDecl.Doc.List {
					matches := externalNotifierRegex.FindStringSubmatch(comment.Text)
					if len(matches) > 1 {
						notifierInfo.Name = matches[1]
						notifierInfo.ConfigKey = strings.ToLower(typeSpec.Name.Name)
						notifierInfo.FileName = strings.ToLower(strings.ReplaceAll(matches[1], " ", "_"))
						found = true

						// Extract fields
						for _, field := range structType.Fields.List {
							if len(field.Names) > 0 {
								fieldName := field.Names[0].Name

								// Get json tag and title from field tags
								var jsonKey, title string
								if field.Tag != nil {
									tagValue := strings.Trim(field.Tag.Value, "`")

									// Extract json key
									jsonRegex := regexp.MustCompile(`json:"([^"]+)"`)
									jsonMatches := jsonRegex.FindStringSubmatch(tagValue)
									if len(jsonMatches) > 1 {
										jsonKey = jsonMatches[1]
									}

									// Extract title
									titleRegex := regexp.MustCompile(`title:"([^"]+)"`)
									titleMatches := titleRegex.FindStringSubmatch(tagValue)
									if len(titleMatches) > 1 {
										title = titleMatches[1]
									}
								}

								if jsonKey == "" {
									jsonKey = strings.ToLower(fieldName)
								}

								if title == "" {
									title = fieldName
								}

								notifierInfo.Fields = append(notifierInfo.Fields, FieldInfo{
									Name:  fieldName,
									Key:   jsonKey,
									Title: title,
								})
							}
						}
						break
					}
				}
			}

			if found {
				break
			}
		}

		if found {
			break
		}
	}

	return notifierInfo, found
}

// Generate TypeScript config file for a notifier
func generateTSConfig(notifier NotifierInfo, outputDir string) error {
	// Create function map for template
	funcMap := template.FuncMap{
		"replaceSpaces": func(s string) string {
			return strings.ReplaceAll(s, " ", "")
		},
	}

	// Create template with function map
	tmpl, err := template.New("tsConfig").Funcs(funcMap).Parse(tsConfigTemplate)
	if err != nil {
		return fmt.Errorf("error creating template: %w", err)
	}

	// Create output file
	outputFile := filepath.Join(outputDir, notifier.FileName+".ts")
	file, err := os.Create(outputFile)
	if err != nil {
		return fmt.Errorf("error creating output file %s: %w", outputFile, err)
	}
	defer file.Close()

	// Execute template
	err = tmpl.Execute(file, notifier)
	if err != nil {
		return fmt.Errorf("error executing template: %w", err)
	}

	logger.Infof("Generated TypeScript config for %s at %s\n", notifier.Name, outputFile)
	return nil
}

// Update index.ts file
func updateIndexFile(notifiers []NotifierInfo, outputDir string) error {
	// Create content for index.ts
	var imports strings.Builder
	var configMap strings.Builder

	// Sort notifiers alphabetically by name for stable output
	sort.Slice(notifiers, func(i, j int) bool {
		return notifiers[i].Name < notifiers[j].Name
	})

	for _, notifier := range notifiers {
		fileName := notifier.FileName
		configName := strings.ReplaceAll(notifier.Name, " ", "") + "Config"

		imports.WriteString(fmt.Sprintf("import %s from './%s'\n", configName, fileName))
	}

	// Generate the map
	configMap.WriteString("const configMap = {\n")
	for _, notifier := range notifiers {
		configKey := strings.ToLower(strings.ReplaceAll(notifier.Name, " ", "_"))
		configMap.WriteString(fmt.Sprintf("  %s: %sConfig", configKey, strings.ReplaceAll(notifier.Name, " ", "")))
		configMap.WriteString(",\n")
	}
	configMap.WriteString("}\n")

	content := fmt.Sprintf("// This file is auto-generated by notification generator. DO NOT EDIT.\n%s\n%s\nexport default configMap\n", imports.String(), configMap.String())

	// Write to index.ts
	indexPath := filepath.Join(outputDir, "index.ts")
	err := os.WriteFile(indexPath, []byte(content), 0644)
	if err != nil {
		return fmt.Errorf("error writing index.ts: %w", err)
	}

	logger.Infof("Updated index.ts at %s\n", indexPath)
	return nil
}
