package agent

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	jsoniter "github.com/json-iterator/go"
	"github.com/yaoapp/gou/application"
	v8 "github.com/yaoapp/gou/runtime/v8"
	"github.com/yaoapp/kun/log"
	"github.com/yaoapp/yao/sui/core"
	"gopkg.in/yaml.v3"
)

// Page wraps core.Page with agent-specific functionality
type Page struct {
	*core.Page
	tmpl        *Template
	assistantID string
	pagesRoot   string
}

// Load load the page content
func (page *Page) Load() error {
	p := page.Page
	fs := page.tmpl.agent.fs

	// Set document from template
	p.Document = page.tmpl.Document

	// Read HTML
	htmlFile := filepath.Join(p.Path, p.Codes.HTML.File)
	if fs.IsFile(htmlFile) {
		content, err := fs.ReadFile(htmlFile)
		if err != nil {
			return err
		}
		p.Codes.HTML.Code = string(content)
	}

	// Read CSS
	cssFile := filepath.Join(p.Path, p.Codes.CSS.File)
	if fs.IsFile(cssFile) {
		content, err := fs.ReadFile(cssFile)
		if err != nil {
			return err
		}
		p.Codes.CSS.Code = string(content)
	}

	// Read JS
	jsFile := filepath.Join(p.Path, p.Codes.JS.File)
	if fs.IsFile(jsFile) {
		content, err := fs.ReadFile(jsFile)
		if err != nil {
			return err
		}
		p.Codes.JS.Code = string(content)
	}

	// Read TS
	tsFile := filepath.Join(p.Path, p.Codes.TS.File)
	if fs.IsFile(tsFile) {
		content, err := fs.ReadFile(tsFile)
		if err != nil {
			return err
		}
		p.Codes.TS.Code = string(content)
	}

	// Read DATA (JSON)
	dataFile := filepath.Join(p.Path, p.Codes.DATA.File)
	if fs.IsFile(dataFile) {
		content, err := fs.ReadFile(dataFile)
		if err != nil {
			return err
		}
		p.Codes.DATA.Code = string(content)
	}

	// Read Config
	confFile := filepath.Join(p.Path, p.Codes.CONF.File)
	if fs.IsFile(confFile) {
		content, err := fs.ReadFile(confFile)
		if err != nil {
			return err
		}
		p.Codes.CONF.Code = string(content)

		// Parse config
		var config core.PageConfig
		if err := jsoniter.Unmarshal(content, &config); err == nil {
			p.Config = &config
		}
	}

	// Load backend script
	err := page.loadScript()
	if err != nil {
		return err
	}

	return nil
}

// loadScript load the backend script
func (page *Page) loadScript() error {
	p := page.Page
	fs := page.tmpl.agent.fs

	// Try .backend.ts first, then .backend.js
	tsFile := filepath.Join(p.Path, fmt.Sprintf("%s.backend.ts", p.Name))
	jsFile := filepath.Join(p.Path, fmt.Sprintf("%s.backend.js", p.Name))

	var scriptFile string
	if fs.IsFile(tsFile) {
		scriptFile = tsFile
	} else if fs.IsFile(jsFile) {
		scriptFile = jsFile
	}

	if scriptFile == "" {
		return nil
	}

	content, err := fs.ReadFile(scriptFile)
	if err != nil {
		return err
	}

	script, err := v8.MakeScript(content, scriptFile, 5*time.Second)
	if err != nil {
		return err
	}

	p.Script = &core.Script{Script: script}
	return nil
}

// Get get the page info
func (page *Page) Get() *core.Page {
	return page.Page
}

// GetConfig get the page config
func (page *Page) GetConfig() *core.PageConfig {
	p := page.Page
	if p.Config != nil {
		return p.Config
	}

	// Try to load config if not loaded
	fs := page.tmpl.agent.fs
	confFile := filepath.Join(p.Path, p.Codes.CONF.File)
	if fs.IsFile(confFile) {
		content, err := fs.ReadFile(confFile)
		if err != nil {
			return page.mergeTemplateConfig(nil)
		}

		var config core.PageConfig
		if err := jsoniter.Unmarshal(content, &config); err == nil {
			p.Config = &config
			return page.mergeTemplateConfig(p.Config)
		}
	}

	return page.mergeTemplateConfig(nil)
}

// mergeTemplateConfig merges template default config into page config (page config takes priority).
// Use guard: "-" in page config to explicitly disable guard inheritance.
func (page *Page) mergeTemplateConfig(cfg *core.PageConfig) *core.PageConfig {
	tmplConfig := page.tmpl.Template.Config
	if tmplConfig == nil {
		return cfg
	}

	if cfg == nil {
		cfg = &core.PageConfig{PageSetting: *tmplConfig}
		page.Page.Config = cfg
		return cfg
	}

	// Merge guard (page config takes priority, "-" means explicitly no guard)
	if cfg.Guard == "" {
		// Page has no guard, use template's guard (with redirect)
		cfg.Guard = tmplConfig.Guard
	} else if !strings.Contains(cfg.Guard, ":") && strings.Contains(tmplConfig.Guard, ":") {
		// Page has guard without redirect (e.g. "oauth"), template has redirect (e.g. "oauth:/login")
		// Inherit redirect from template if same guard type
		tmplParts := strings.SplitN(tmplConfig.Guard, ":", 2)
		if tmplParts[0] == cfg.Guard {
			cfg.Guard = tmplConfig.Guard
		}
	}

	// Merge API guard config
	if cfg.API == nil && tmplConfig.API != nil {
		cfg.API = tmplConfig.API
	}

	return cfg
}

// SaveTemp save the page temporarily (not supported for agent pages)
func (page *Page) SaveTemp(request *core.RequestSource) error {
	return fmt.Errorf("SaveTemp is not supported for agent pages")
}

// Save save the page (not supported for agent pages)
func (page *Page) Save(request *core.RequestSource) error {
	return fmt.Errorf("Save is not supported for agent pages")
}

// SaveAs save the page as (not supported for agent pages)
func (page *Page) SaveAs(route string, setting *core.PageSetting) (core.IPage, error) {
	return nil, fmt.Errorf("SaveAs is not supported for agent pages")
}

// Remove remove the page (not supported for agent pages)
func (page *Page) Remove() error {
	return fmt.Errorf("Remove is not supported for agent pages")
}

// SUI get the SUI interface
func (page *Page) SUI() (core.SUI, error) {
	return page.tmpl.agent, nil
}

// Sid get the session id
func (page *Page) Sid() (string, error) {
	return page.tmpl.agent.DSL.Sid, nil
}

// Template get the template
func (page *Page) Template() core.ITemplate {
	return page.tmpl
}

// AssetScript get the script
func (page *Page) AssetScript() (*core.Asset, error) {
	fs := page.tmpl.agent.fs

	// Try .ts first, then .js
	tsFile := filepath.Join(page.Path, page.Codes.TS.File)
	if fs.IsFile(tsFile) {
		tsCode, err := fs.ReadFile(tsFile)
		if err != nil {
			return nil, err
		}

		jsCode, _, err := page.CompileTS(tsCode, false)
		if err != nil {
			return nil, err
		}

		return &core.Asset{
			Type:    "text/javascript; charset=utf-8",
			Content: []byte(jsCode),
		}, nil
	}

	jsFile := filepath.Join(page.Path, page.Codes.JS.File)
	if fs.IsFile(jsFile) {
		jsCode, err := fs.ReadFile(jsFile)
		if err != nil {
			return nil, err
		}

		jsCode, _, err = page.CompileJS(jsCode, false)
		if err != nil {
			return nil, err
		}

		return &core.Asset{
			Type:    "text/javascript; charset=utf-8",
			Content: jsCode,
		}, nil
	}

	return nil, fmt.Errorf("%s script not found", page.Route)
}

// AssetStyle get the style
func (page *Page) AssetStyle() (*core.Asset, error) {
	fs := page.tmpl.agent.fs

	cssFile := filepath.Join(page.Path, page.Codes.CSS.File)
	if fs.IsFile(cssFile) {
		cssCode, err := fs.ReadFile(cssFile)
		if err != nil {
			return nil, err
		}

		cssCode, err = page.CompileCSS(cssCode, false)
		if err != nil {
			return nil, err
		}

		return &core.Asset{
			Type:    "text/css; charset=utf-8",
			Content: cssCode,
		}, nil
	}
	return nil, fmt.Errorf("%s style not found", page.Route)
}

// Build build the page
func (page *Page) Build(globalCtx *core.GlobalBuildContext, option *core.BuildOption) ([]string, error) {
	ctx := core.NewBuildContext(globalCtx)
	root := option.PublicRoot
	if root == "" {
		root = page.tmpl.agent.DSL.Public.Root
	}

	if option.AssetRoot == "" {
		option.AssetRoot = filepath.Join(root, "assets")
	}
	page.Root = root

	// Load page if not loaded
	if page.Codes.HTML.Code == "" {
		if err := page.Load(); err != nil {
			return nil, err
		}
	}

	// Merge template default config before compile (page config takes priority)
	page.GetConfig()

	html, config, warnings, err := page.Page.Compile(ctx, option)
	if err != nil {
		return warnings, fmt.Errorf("Compile the page %s error: %s", page.Route, err.Error())
	}

	// Save the html
	err = page.writeHTML([]byte(html), option.Data)
	if err != nil {
		return warnings, fmt.Errorf("Write the page %s error: %s", page.Route, err.Error())
	}

	// Save the backend script file
	err = page.writeBackendScript(option.Data)
	if err != nil {
		return warnings, fmt.Errorf("Write the backend script file error: %s", err.Error())
	}

	// Save the config file
	err = page.writeConfig([]byte(config), option.Data)
	if err != nil {
		return warnings, err
	}

	// Write locale files from page's __locales directory
	err = page.writeLocaleFiles(ctx, option.Data)
	if err != nil {
		log.Warn("[Agent] Write locale files error: %s", err.Error())
		// Don't fail the build for locale errors
	}

	return warnings, nil
}

// publicFile get the public file path
func (page *Page) publicFile(data map[string]interface{}) string {
	root, err := page.tmpl.agent.DSL.PublicRoot(data)
	if err != nil {
		log.Error("publicFile: Get the public root error: %s. use %s", err.Error(), page.tmpl.agent.DSL.Public.Root)
		root = page.tmpl.agent.DSL.Public.Root
	}
	return filepath.Join("/", "public", root, page.Route)
}

// writeHTML write the html to file
func (page *Page) writeHTML(html []byte, data map[string]interface{}) error {
	htmlFile := fmt.Sprintf("%s.sui", page.publicFile(data))
	htmlFileAbs := filepath.Join(application.App.Root(), htmlFile)
	dir := filepath.Dir(htmlFileAbs)
	if exist, _ := os.Stat(dir); exist == nil {
		os.MkdirAll(dir, os.ModePerm)
	}
	err := os.WriteFile(htmlFileAbs, html, 0644)
	if err != nil {
		return err
	}

	core.RemoveCache(htmlFile)
	return nil
}

// writeConfig write the config to file
func (page *Page) writeConfig(config []byte, data map[string]interface{}) error {
	configFile := fmt.Sprintf("%s.cfg", page.publicFile(data))
	configFileAbs := filepath.Join(application.App.Root(), configFile)
	dir := filepath.Dir(configFileAbs)
	if exist, _ := os.Stat(dir); exist == nil {
		os.MkdirAll(dir, os.ModePerm)
	}
	err := os.WriteFile(configFileAbs, config, 0644)
	if err != nil {
		return err
	}
	return nil
}

// backendScriptSource get the backend script source
func (page *Page) backendScriptSource() (string, []byte, error) {
	fs := page.tmpl.agent.fs
	backendFile := filepath.Join(page.Path, fmt.Sprintf("%s.backend.ts", page.Name))
	if !fs.IsFile(backendFile) {
		backendFile = filepath.Join(page.Path, fmt.Sprintf("%s.backend.js", page.Name))
	}

	if !fs.IsFile(backendFile) {
		return "", nil, nil
	}

	source, err := fs.ReadFile(backendFile)
	if err != nil {
		return "", nil, err
	}

	source = []byte(fmt.Sprintf("%s\n%s", source, core.BackendScript(page.Route)))
	return backendFile, source, nil
}

// writeBackendScript write the backend script to file
func (page *Page) writeBackendScript(data map[string]interface{}) error {
	file, source, err := page.backendScriptSource()
	if err != nil {
		return err
	}

	if source == nil {
		return nil
	}

	ext := filepath.Ext(file)
	scriptFile := fmt.Sprintf("%s.backend%s", page.publicFile(data), ext)
	scriptFileAbs := filepath.Join(application.App.Root(), scriptFile)
	dir := filepath.Dir(scriptFileAbs)
	if exist, _ := os.Stat(dir); exist == nil {
		os.MkdirAll(dir, os.ModePerm)
	}

	err = os.WriteFile(scriptFileAbs, []byte(source), 0644)
	if err != nil {
		return err
	}
	core.RemoveCache(scriptFile)
	return nil
}

// BuildAsComponent build the page as component
func (page *Page) BuildAsComponent(globalCtx *core.GlobalBuildContext, option *core.BuildOption) ([]string, error) {
	warnings := []string{}

	if option.AssetRoot == "" {
		root, err := page.tmpl.agent.DSL.PublicRoot(option.Data)
		if err != nil {
			return warnings, err
		}
		option.AssetRoot = root
	}

	// Load page if not loaded
	if page.Codes.HTML.Code == "" {
		if err := page.Load(); err != nil {
			return nil, err
		}
	}

	// BuildAsComponent needs a parent selection, which is handled by the caller
	return warnings, nil
}

// Trans translate the page
func (page *Page) Trans(globalCtx *core.GlobalBuildContext, option *core.BuildOption) ([]string, error) {
	warnings := []string{}
	ctx := core.NewBuildContext(globalCtx)

	// Merge template default config before compile
	page.GetConfig()

	_, _, messages, err := page.Page.Compile(ctx, option)
	if err != nil {
		return warnings, err
	}

	// Save translations - messages is []string, not map
	log.Debug("[Agent] Page %s translation messages: %v", page.Route, messages)

	return warnings, nil
}

// AssetRoot get the asset root for this page
func (page *Page) AssetRoot() string {
	// If this is an assistant page, check for assistant-specific assets
	if page.assistantID != "" {
		assistantAssetsDir := filepath.Join(page.pagesRoot, "__assets")
		if page.tmpl.agent.fs.IsDir(assistantAssetsDir) {
			return fmt.Sprintf("/%s/assets", page.assistantID)
		}
	}

	// Default to global agent assets
	return "/assets"
}

// AssistantID get the assistant ID (empty for global agent pages)
func (page *Page) AssistantID() string {
	return page.assistantID
}

// writeLocaleFiles writes locale files from page's __locales directory to public,
// merging script translations extracted from __m() calls during build.
func (page *Page) writeLocaleFiles(ctx *core.BuildContext, data map[string]interface{}) error {
	fs := page.tmpl.agent.fs

	// Check if page has __locales directory
	localesDir := filepath.Join(page.Path, "__locales")
	if !fs.IsDir(localesDir) {
		return nil
	}

	// Get translations from build context (includes __m() calls marked as type "script")
	var translations []core.Translation
	if ctx != nil {
		translations = ctx.GetTranslations()
	}
	prefix := core.TranslationKeyPrefix(page.Route)

	// Get the public root
	root, err := page.tmpl.agent.DSL.PublicRoot(data)
	if err != nil {
		log.Error("writeLocaleFiles: Get the public root error: %s. use %s", err.Error(), page.tmpl.agent.DSL.Public.Root)
		root = page.tmpl.agent.DSL.Public.Root
	}

	// Read all locale files in __locales directory
	files, err := fs.ReadDir(localesDir, false)
	if err != nil {
		return err
	}

	for _, file := range files {
		// Skip directories
		if fs.IsDir(file) {
			continue
		}

		// Only process .yml files
		if filepath.Ext(file) != ".yml" {
			continue
		}

		// Get locale name (e.g., "zh-cn" from "zh-cn.yml")
		localeName := filepath.Base(file)
		localeName = localeName[:len(localeName)-4] // Remove .yml extension

		// Read the locale file
		content, err := fs.ReadFile(file)
		if err != nil {
			log.Error("[Agent] Read locale file error: %s", err.Error())
			continue
		}

		// Parse the locale file
		var localeData map[string]interface{}
		err = yaml.Unmarshal(content, &localeData)
		if err != nil {
			log.Error("[Agent] Parse locale file error: %s", err.Error())
			continue
		}

		// Convert to the format expected by core.Locale
		locale := core.Locale{
			Name:           localeName,
			Keys:           map[string]string{},
			Messages:       map[string]string{},
			ScriptMessages: map[string]string{},
		}

		// Extract messages
		if messages, ok := localeData["messages"].(map[string]interface{}); ok {
			for k, v := range messages {
				if strVal, ok := v.(string); ok {
					locale.Messages[k] = strVal
				}
			}
		}

		// Extract script_messages (if manually specified in source locale)
		if scriptMessages, ok := localeData["script_messages"].(map[string]interface{}); ok {
			for k, v := range scriptMessages {
				if strVal, ok := v.(string); ok {
					locale.ScriptMessages[k] = strVal
				}
			}
		}

		// Merge translations from build context — this populates ScriptMessages
		// from __m() calls found in TS/JS scripts during compilation
		if len(translations) > 0 {
			locale.MergeTranslations(translations, prefix)
		}

		// Extract timezone and direction
		if tz, ok := localeData["timezone"].(string); ok {
			locale.Timezone = tz
		}
		if dir, ok := localeData["direction"].(string); ok {
			locale.Direction = dir
		}

		// Write to public/.locales/<locale>/<route>.yml
		// page.Route may contain path like /expense/test, so we need to create nested directories
		targetFile := filepath.Join(application.App.Root(), "public", root, ".locales", localeName, fmt.Sprintf("%s.yml", page.Route))
		targetDir := filepath.Dir(targetFile)
		if exist, _ := os.Stat(targetDir); exist == nil {
			os.MkdirAll(targetDir, os.ModePerm)
		}

		localeContent, err := yaml.Marshal(locale)
		if err != nil {
			log.Error("[Agent] Marshal locale error: %s", err.Error())
			continue
		}

		err = os.WriteFile(targetFile, localeContent, 0644)
		if err != nil {
			log.Error("[Agent] Write locale file error: %s", err.Error())
			continue
		}

		log.Info("[Agent] Wrote locale file: %s", targetFile)
	}

	return nil
}
