package server

import (
	"bufio"
	"embed"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"odoo-go/pkg/tools"
)

//go:embed assets_js.txt
var assetsJSFile embed.FS

//go:embed assets_css.txt
var assetsCSSFile embed.FS

//go:embed assets_xml.txt
var assetsXMLFile embed.FS

var jsFiles []string
var cssFiles []string
var xmlFiles []string

func init() {
	jsFiles = loadAssetList("assets_js.txt", assetsJSFile)
	cssFiles = loadAssetList("assets_css.txt", assetsCSSFile)
	xmlFiles = loadAssetList("assets_xml.txt", assetsXMLFile)
}

// loadXMLTemplate reads an XML template file from the frontend directory.
func loadXMLTemplate(cfg *tools.Config, urlPath string) string {
	if cfg.FrontendDir == "" {
		return ""
	}
	rel := strings.TrimPrefix(urlPath, "/")
	fullPath := filepath.Join(cfg.FrontendDir, rel)
	data, err := os.ReadFile(fullPath)
	if err == nil {
		return string(data)
	}
	return ""
}

func loadAssetList(name string, fs embed.FS) []string {
	data, err := fs.ReadFile(name)
	if err != nil {
		return nil
	}
	var files []string
	scanner := bufio.NewScanner(strings.NewReader(string(data)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line != "" && !strings.HasPrefix(line, "#") {
			files = append(files, line)
		}
	}
	return files
}

// handleWebClient serves the Odoo webclient HTML shell.
// Mirrors: odoo/addons/web/controllers/home.py Home.web_client()
func (s *Server) handleWebClient(w http.ResponseWriter, r *http.Request) {
	// Check if database needs initialization
	// Mirrors: odoo/addons/web/controllers/home.py ensure_db()
	if s.isSetupNeeded() {
		http.Redirect(w, r, "/web/database/manager", http.StatusFound)
		return
	}

	// Check authentication
	sess := GetSession(r)
	if sess == nil {
		// Try cookie directly
		cookie, err := r.Cookie("session_id")
		if err != nil || cookie.Value == "" {
			http.Redirect(w, r, "/web/login", http.StatusFound)
			return
		}
		sess = s.sessions.Get(cookie.Value)
		if sess == nil {
			http.Redirect(w, r, "/web/login", http.StatusFound)
			return
		}
	}

	sessionInfo := s.buildSessionInfo(sess)
	sessionInfoJSON, _ := json.Marshal(sessionInfo)

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Cache-Control", "no-store")

	// Build script tags: module_loader must load first (defines odoo.loader),
	// then the concatenated bundle serves everything else in one request.
	// We suppress transient missing-dependency errors during loading by
	// temporarily replacing reportErrors with a no-op, then restore it
	// after the bundle has loaded.
	var scriptTags strings.Builder
	cacheBuster := fmt.Sprintf("?v=%d", time.Now().Unix())

	// 1) module_loader.js — must run first to define odoo.define/odoo.loader
	scriptTags.WriteString(fmt.Sprintf("    <script type=\"text/javascript\" src=\"/web/static/src/module_loader.js%s\"></script>\n", cacheBuster))

	// 2) Suppress transient reportErrors while the bundle loads
	scriptTags.WriteString("    <script>if (odoo.loader) { odoo.loader.__origReportErrors = odoo.loader.reportErrors.bind(odoo.loader); odoo.loader.reportErrors = function() {}; }</script>\n")

	// 3) The concatenated JS bundle (all other modules + XML templates)
	scriptTags.WriteString(fmt.Sprintf("    <script type=\"text/javascript\" src=\"/web/assets/bundle.js%s\"></script>\n", cacheBuster))

	// 4) Restore reportErrors and run a final check for genuine errors
	scriptTags.WriteString("    <script>if (odoo.loader && odoo.loader.__origReportErrors) { odoo.loader.reportErrors = odoo.loader.__origReportErrors; odoo.loader.reportErrors(odoo.loader.findErrors()); }</script>\n")

	// Build link tags for CSS: compiled SCSS bundle + individual CSS files
	var linkTags strings.Builder
	// Main compiled SCSS bundle (Bootstrap + Odoo core styles)
	linkTags.WriteString(fmt.Sprintf("    <link rel=\"stylesheet\" href=\"/web/static/odoo_web.css%s\"/>\n", cacheBuster))
	// Additional plain CSS files
	for _, src := range cssFiles {
		if strings.HasSuffix(src, ".css") {
			linkTags.WriteString(fmt.Sprintf("    <link rel=\"stylesheet\" href=\"%s%s\"/>\n", src, cacheBuster))
		}
	}

	// XML templates are compiled to JS (registerTemplate calls) and included
	// in the JS bundle as xml_templates_bundle.js — no inline XML needed.

	fmt.Fprintf(w, `<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <title>Odoo</title>
    <link rel="shortcut icon" href="/web/static/img/favicon.ico" type="image/x-icon"/>

%s
    <script>
    var odoo = {
        csrf_token: "dummy",
        debug: "assets",
        __session_info__: %s,
        reloadMenus: function() {
            return fetch("/web/webclient/load_menus", {
                method: "GET",
                headers: {"Content-Type": "application/json"}
            }).then(function(r) { return r.json(); });
        }
    };
    odoo.loadMenusPromise = odoo.reloadMenus();

    // Catch unhandled errors and log them
    window.addEventListener('unhandledrejection', function(e) {
        console.error('[odoo-go] Unhandled rejection:', e.reason);
    });

    // Patch OWL to prevent infinite error-dialog recursion.
    window.__errorDialogCount = 0;
    document.addEventListener('DOMContentLoaded', function() {
        if (typeof owl !== 'undefined' && owl.App) {
            var _orig = owl.App.prototype.handleError;
            owl.App.prototype.handleError = function() {
                window.__errorDialogCount++;
                if (window.__errorDialogCount > 3) {
                    console.error('[odoo-go] Error dialog recursion stopped.');
                    return;
                }
                return _orig.apply(this, arguments);
            };
        }
    });
    </script>

%s</head>
<body class="o_web_client">
</body>
</html>`, linkTags.String(), sessionInfoJSON, scriptTags.String())
}

// buildSessionInfo constructs the session_info JSON object expected by the webclient.
// Mirrors: odoo/addons/web/models/ir_http.py session_info()
func (s *Server) buildSessionInfo(sess *Session) map[string]interface{} {
	return map[string]interface{}{
		"session_id":        sess.ID,
		"uid":              sess.UID,
		"is_system":        sess.UID == 1,
		"is_admin":         sess.UID == 1,
		"is_public":        false,
		"is_internal_user": true,
		"user_context": map[string]interface{}{
			"lang":                "en_US",
			"tz":                 "UTC",
			"allowed_company_ids": []int64{sess.CompanyID},
		},
		"db":                   s.config.DBName,
		"registry_hash":        fmt.Sprintf("odoo-go-%d", time.Now().Unix()),
		"server_version":       "19.0-go",
		"server_version_info":  []interface{}{19, 0, 0, "final", 0, "g"},
		"name":                 sess.Login,
		"username":             sess.Login,
		"partner_id":           sess.UID + 1, // Simplified mapping
		"partner_display_name": sess.Login,
		"partner_write_date":   "2026-01-01 00:00:00",
		"quick_login":          true,
		"web.base.url":         fmt.Sprintf("http://localhost:%d", s.config.HTTPPort),
		"active_ids_limit":     20000,
		"max_file_upload_size": 134217728,
		"home_action_id":       1,
		"current_menu":         1,
		"support_url":          "",
		"notification_type":    "email",
		"display_switch_company_menu": false,
		"test_mode":            false,
		"show_effect":          true,
		"currencies": map[string]interface{}{
			"1": map[string]interface{}{
				"id": 1, "name": "EUR", "symbol": "€",
				"position": "after", "digits": []int{69, 2},
			},
		},
		"bundle_params": map[string]interface{}{
			"lang":  "en_US",
			"debug": "assets",
		},
		"user_companies": map[string]interface{}{
			"current_company": sess.CompanyID,
			"allowed_companies": map[string]interface{}{
				fmt.Sprintf("%d", sess.CompanyID): map[string]interface{}{
					"id":          sess.CompanyID,
					"name":        "My Company",
					"sequence":    10,
					"child_ids":   []int64{},
					"parent_id":   false,
					"currency_id": 1,
				},
			},
			"disallowed_ancestor_companies": map[string]interface{}{},
		},
		"user_settings": map[string]interface{}{
			"id":      1,
			"user_id": map[string]interface{}{"id": sess.UID, "display_name": sess.Login},
		},
		"view_info": map[string]interface{}{
			"list":     map[string]interface{}{"display_name": "List", "icon": "oi oi-view-list", "multi_record": true},
			"form":     map[string]interface{}{"display_name": "Form", "icon": "fa fa-address-card", "multi_record": false},
			"kanban":   map[string]interface{}{"display_name": "Kanban", "icon": "oi oi-view-kanban", "multi_record": true},
			"graph":    map[string]interface{}{"display_name": "Graph", "icon": "fa fa-area-chart", "multi_record": true},
			"pivot":    map[string]interface{}{"display_name": "Pivot", "icon": "oi oi-view-pivot", "multi_record": true},
			"calendar": map[string]interface{}{"display_name": "Calendar", "icon": "fa fa-calendar", "multi_record": true},
			"search":   map[string]interface{}{"display_name": "Search", "icon": "oi oi-search", "multi_record": true},
		},
		"groups": map[string]interface{}{
			"base.group_allow_export": true,
			"base.group_user":         true,
			"base.group_system":       true,
		},
	}
}

// handleTranslations returns translations for the requested language.
// Mirrors: odoo/addons/web/controllers/webclient.py translations()
//
// The web client calls this with params like {mods: ["web","base",...], lang: "de_DE"}.
// We load translations from ir_translation table and return them in Odoo's format.
func (s *Server) handleTranslations(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Cache-Control", "public, max-age=3600")

	// Determine requested language from POST body or session context
	lang := "en_US"
	if r.Method == http.MethodPost {
		var req JSONRPCRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err == nil {
			var params struct {
				Mods []string `json:"mods"`
				Lang string   `json:"lang"`
			}
			if err := json.Unmarshal(req.Params, &params); err == nil && params.Lang != "" {
				lang = params.Lang
			}
		}
	}
	if q := r.URL.Query().Get("lang"); q != "" {
		lang = q
	}

	// Default lang parameters (English)
	langParams := map[string]interface{}{
		"direction":     "ltr",
		"date_format":   "%%m/%%d/%%Y",
		"time_format":   "%%H:%%M:%%S",
		"grouping":      "[3,0]",
		"decimal_point": ".",
		"thousands_sep": ",",
		"week_start":    1,
	}

	// Try to load language parameters from res_lang
	var dateFormat, timeFormat, decimalPoint, thousandsSep, direction string
	err := s.pool.QueryRow(r.Context(),
		`SELECT date_format, time_format, decimal_point, thousands_sep, direction
		 FROM res_lang WHERE code = $1 AND active = true`, lang,
	).Scan(&dateFormat, &timeFormat, &decimalPoint, &thousandsSep, &direction)
	if err == nil {
		// Convert Go-style format markers to Python-style (double-%) for the web client
		langParams["date_format"] = dateFormat
		langParams["time_format"] = timeFormat
		langParams["decimal_point"] = decimalPoint
		langParams["thousands_sep"] = thousandsSep
		langParams["direction"] = direction
	}

	// Load translations from ir_translation
	modules := make(map[string]interface{})
	multiLang := false

	// Check if translations exist for this language
	rows, err := s.pool.Query(r.Context(),
		`SELECT COALESCE(module, ''), src, value FROM ir_translation
		 WHERE lang = $1 AND value != '' AND value IS NOT NULL
		 ORDER BY module, name`, lang)
	if err == nil {
		defer rows.Close()
		// Group translations by module
		modMessages := make(map[string][]map[string]string)
		for rows.Next() {
			var module, src, value string
			if err := rows.Scan(&module, &src, &value); err != nil {
				continue
			}
			if module == "" {
				module = "web"
			}
			modMessages[module] = append(modMessages[module], map[string]string{
				"id":     src,
				"string": value,
			})
		}
		for mod, msgs := range modMessages {
			modules[mod] = map[string]interface{}{
				"messages": msgs,
			}
			multiLang = true
		}
	}

	// Check if more than one active language exists
	var langCount int
	if err := s.pool.QueryRow(r.Context(),
		`SELECT COUNT(*) FROM res_lang WHERE active = true`).Scan(&langCount); err == nil {
		if langCount > 1 {
			multiLang = true
		}
	}

	json.NewEncoder(w).Encode(map[string]interface{}{
		"lang":            lang,
		"hash":            fmt.Sprintf("odoo-go-%s", lang),
		"lang_parameters": langParams,
		"modules":         modules,
		"multi_lang":      multiLang,
	})
}
