/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package router

import (
	"embed"
	"fmt"
	"github.com/apache/answer/plugin"
	"io/fs"
	"net/http"
	"os"
	"strings"

	"github.com/apache/answer/internal/controller"
	"github.com/apache/answer/internal/service/siteinfo_common"
	"github.com/apache/answer/pkg/htmltext"
	"github.com/apache/answer/ui"
	"github.com/gin-gonic/gin"
	"github.com/segmentfault/pacman/log"
)

const UIIndexFilePath = "build/index.html"
const UIRootFilePath = "build"
const UIStaticPath = "build/static"

// UIRouter is an interface that provides ui static file routers
type UIRouter struct {
	siteInfoController *controller.SiteInfoController
	siteInfoService    siteinfo_common.SiteInfoCommonService
}

// NewUIRouter creates a new UIRouter instance with the embed resources
func NewUIRouter(
	siteInfoController *controller.SiteInfoController,
	siteInfoService siteinfo_common.SiteInfoCommonService,
) *UIRouter {
	return &UIRouter{
		siteInfoController: siteInfoController,
		siteInfoService:    siteInfoService,
	}
}

// _resource is an interface that provides static file, it's a private interface
type _resource struct {
	fs embed.FS
}

// Open to implement the interface by http.FS required
func (r *_resource) Open(name string) (fs.File, error) {
	name = fmt.Sprintf(UIStaticPath+"/%s", name)
	log.Debugf("open static path %s", name)
	return r.fs.Open(name)
}

// Register a new static resource which generated by ui directory
func (a *UIRouter) Register(r *gin.Engine, baseURLPath string) {
	staticPath := os.Getenv("ANSWER_STATIC_PATH")

	// if ANSWER_STATIC_PATH is set and not empty, ignore embed resource
	if staticPath != "" {
		info, err := os.Stat(staticPath)

		if err != nil || !info.IsDir() {
			log.Error(err)
		} else {
			log.Debugf("registering static path %s", staticPath)

			r.LoadHTMLGlob(staticPath + "/*.html")
			r.Static(baseURLPath+"/static", staticPath+"/static")
			r.NoRoute(func(c *gin.Context) {
				c.HTML(http.StatusOK, "index.html", gin.H{})
			})

			// return immediately if the static path is set
			return
		}
	}

	// handle the static file by default ui static files
	r.StaticFS(baseURLPath+"/static", http.FS(&_resource{
		fs: ui.Build,
	}))

	// specify the not router for default routes and redirect
	r.NoRoute(func(c *gin.Context) {
		urlPath := c.Request.URL.Path
		if len(baseURLPath) > 0 {
			urlPath = strings.TrimPrefix(urlPath, baseURLPath)
		}
		filePath := ""
		switch urlPath {
		case "/favicon.ico":
			branding, err := a.siteInfoService.GetSiteBranding(c)
			if err != nil {
				log.Error(err)
			}
			if branding.Favicon != "" {
				c.String(http.StatusOK, htmltext.GetPicByUrl(branding.Favicon))
				return
			} else if branding.SquareIcon != "" {
				c.String(http.StatusOK, htmltext.GetPicByUrl(branding.SquareIcon))
				return
			} else {
				c.Header("content-type", "image/vnd.microsoft.icon")
				filePath = UIRootFilePath + urlPath
			}
		case "/manifest.json":
			a.siteInfoController.GetManifestJson(c)
			return
		case "/install":
			// if answer is running by run command user can not access install page.
			c.Redirect(http.StatusFound, "/")
			return
		default:
			filePath = UIIndexFilePath
			c.Header("content-type", "text/html;charset=utf-8")
			c.Header("X-Frame-Options", "DENY")
		}
		file, err := ui.Build.ReadFile(filePath)
		if err != nil {
			log.Error(err)
			c.Status(http.StatusNotFound)
			return
		}

		cdnPrefix := ""
		_ = plugin.CallCDN(func(fn plugin.CDN) error {
			cdnPrefix = fn.GetStaticPrefix()
			return nil
		})
		if cdnPrefix != "" {
			if cdnPrefix[len(cdnPrefix)-1:] == "/" {
				cdnPrefix = strings.TrimSuffix(cdnPrefix, "/")
			}
			c.String(http.StatusOK, strings.ReplaceAll(string(file), "/static", cdnPrefix+"/static"))
			return
		}

		// This part is to solve the problem of returning 404 when the access path does not exist.
		// However, there is no way to check whether the current route exists in the frontend.
		// We can only hand over the route to the frontend for processing.
		// And the plugin, frontend routes can now be dynamically registered,
		// so there's no good way to get all frontend routes
		//if filePath == UIIndexFilePath {
		//	c.String(http.StatusNotFound, string(file))
		//	return
		//}

		c.String(http.StatusOK, string(file))
	})
}
