package website

import (
	"errors"
	"net/http"
	"net/url"
	"strings"

	"git.handmade.network/hmn/hmn/src/auth"
	"git.handmade.network/hmn/hmn/src/config"
	"git.handmade.network/hmn/hmn/src/db"
	"git.handmade.network/hmn/hmn/src/hmndata"
	"git.handmade.network/hmn/hmn/src/hmnurl"
	"git.handmade.network/hmn/hmn/src/logging"
	"git.handmade.network/hmn/hmn/src/models"
	"git.handmade.network/hmn/hmn/src/oops"
	"git.handmade.network/hmn/hmn/src/templates"
)

func loadCommonData(h Handler) Handler {
	return func(c *RequestContext) ResponseData {
		c.Perf.StartBlock("MIDDLEWARE", "Load common website data")
		{
			// get user
			{
				sessionCookie, err := c.Req.Cookie(auth.SessionCookieName)
				if err == nil {
					user, session, err := getCurrentUserAndSession(c, sessionCookie.Value)
					if err != nil {
						return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to get current user"))
					}

					c.CurrentUser = user
					c.CurrentSession = session
				}
				// http.ErrNoCookie is the only error Cookie ever returns, so no further handling to do here.
			}

			// get current official project (HMN or otherwise, by subdomain)
			{
				hostPrefix := strings.TrimSuffix(c.Req.Host, hmnurl.GetBaseHost())
				slug := strings.TrimRight(hostPrefix, ".")
				var owners []*models.User

				if len(slug) > 0 {
					dbProject, err := hmndata.FetchProjectBySlug(c, c.Conn, c.CurrentUser, slug, hmndata.ProjectsQuery{
						Lifecycles:    models.AllProjectLifecycles,
						IncludeHidden: true,
					})
					if err == nil {
						c.CurrentProject = &dbProject.Project
						c.CurrentProjectLogoUrl = templates.ProjectLogoUrl(&dbProject.Project, dbProject.LogoLightAsset, dbProject.LogoDarkAsset, c.Theme)
						owners = dbProject.Owners
					} else {
						if errors.Is(err, db.NotFound) {
							// do nothing, this is fine
						} else {
							return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch current project"))
						}
					}
				}

				if c.CurrentProject == nil {
					dbProject, err := hmndata.FetchProject(c, c.Conn, c.CurrentUser, models.HMNProjectID, hmndata.ProjectsQuery{
						Lifecycles:    models.AllProjectLifecycles,
						IncludeHidden: true,
					})
					if err != nil {
						panic(oops.New(err, "failed to fetch HMN project"))
					}
					c.CurrentProject = &dbProject.Project
					c.CurrentProjectLogoUrl = templates.ProjectLogoUrl(&dbProject.Project, dbProject.LogoLightAsset, dbProject.LogoDarkAsset, c.Theme)
				}

				if c.CurrentProject == nil {
					panic("failed to load project data")
				}

				c.CurrentUserCanEditCurrentProject = CanEditProject(c.CurrentUser, owners)

				c.UrlContext = hmndata.UrlContextForProject(c.CurrentProject)
			}

			c.Theme = "light"
			if c.CurrentUser != nil && c.CurrentUser.DarkTheme {
				c.Theme = "dark"
			}
		}
		c.Perf.EndBlock()

		return h(c)
	}
}

// Given a session id, fetches user data from the database. Will return nil if
// the user cannot be found, and will only return an error if it's serious.
func getCurrentUserAndSession(c *RequestContext, sessionId string) (*models.User, *models.Session, error) {
	session, err := auth.GetSession(c, c.Conn, sessionId)
	if err != nil {
		if errors.Is(err, auth.ErrNoSession) {
			return nil, nil, nil
		} else {
			return nil, nil, oops.New(err, "failed to get current session")
		}
	}

	user, err := hmndata.FetchUserByUsername(c, c.Conn, nil, session.Username, hmndata.UsersQuery{
		AnyStatus: true,
	})
	if err != nil {
		if errors.Is(err, db.NotFound) {
			logging.Debug().Str("username", session.Username).Msg("returning no current user for this request because the user for the session couldn't be found")
			return nil, nil, nil // user was deleted or something
		} else {
			return nil, nil, oops.New(err, "failed to get user for session")
		}
	}

	return user, session, nil
}

func addCORSHeaders(c *RequestContext, res *ResponseData) {
	parsed, err := url.Parse(config.Config.BaseUrl)
	if err != nil {
		c.Logger.Error().Str("Config.BaseUrl", config.Config.BaseUrl).Msg("Config.BaseUrl cannot be parsed. Skipping CORS headers")
		return
	}
	origin := ""
	origins, found := c.Req.Header["Origin"]
	if found {
		origin = origins[0]
	}
	if strings.HasSuffix(origin, parsed.Host) {
		res.Header().Add("Access-Control-Allow-Origin", origin)
		res.Header().Add("Access-Control-Allow-Credentials", "true")
		res.Header().Add("Vary", "Origin")
	}
}
