package httpserver

import (
	"errors"
	"fmt"
	"mime/multipart"
	"net/http"
	"strconv"

	"update_server/internal/db"
	"update_server/internal/releases"
)

func (h *handler) adminProjects(w http.ResponseWriter, r *http.Request) {
	projects, err := h.store.Projects.List(r.Context())
	if err != nil {
		http.Error(w, "project listing failed", http.StatusInternalServerError)
		return
	}

	data := ProjectsPageData{
		PageData: h.basePageData(
			r,
			"Projects",
			"Protected Admin",
			"Projects",
			"Create projects, update their metadata, archive them when needed, and open each project to manage tags and release uploads.",
		),
		Projects: projects,
	}
	data.Flash = projectListFlash(r.URL.Query().Get("status"))

	renderPage(w, h.renderer, "projects", data)
}

func (h *handler) adminProjectNew(w http.ResponseWriter, r *http.Request) {
	data := ProjectFormPageData{
		PageData: h.basePageData(
			r,
			"New Project",
			"Protected Admin",
			"Create Project",
			"Add a new update stream with a stable slug so future releases and API access rules can target it reliably.",
		),
		Form: ProjectFormData{
			Action:      "/admin/projects",
			SubmitLabel: "Create project",
		},
	}

	renderPage(w, h.renderer, "project_form", data)
}

func (h *handler) adminProjectCreate(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, "invalid project form", http.StatusBadRequest)
		return
	}

	form := ProjectFormData{
		Action:      "/admin/projects",
		SubmitLabel: "Create project",
		Name:        r.FormValue("name"),
		Slug:        r.FormValue("slug"),
		Description: r.FormValue("description"),
	}

	params, validationError := projectFormInput(form.Name, form.Slug, form.Description)
	form.Name = params.Name
	form.Slug = params.Slug
	form.Description = params.Description
	if validationError != "" {
		form.Error = validationError
		h.renderProjectFormPage(w, r, http.StatusBadRequest, form)
		return
	}

	project, err := h.store.Projects.Create(r.Context(), params)
	if err != nil {
		if errors.Is(err, db.ErrConflict) {
			form.Error = "A project with that slug already exists."
			h.renderProjectFormPage(w, r, http.StatusConflict, form)
			return
		}

		http.Error(w, "project creation failed", http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/admin/projects/%d?status=project-created", project.ID), http.StatusSeeOther)
}

func (h *handler) adminProjectDetail(w http.ResponseWriter, r *http.Request) {
	projectID, err := routeID(r, "projectID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	data, err := h.projectDetailPageData(r, projectID, ProjectFormData{}, ReleaseUploadData{})
	if err != nil {
		h.renderProjectDetailError(w, r, err)
		return
	}

	data.Flash = projectDetailFlash(r.URL.Query().Get("status"))
	renderPage(w, h.renderer, "project_detail", *data)
}

func (h *handler) adminProjectUpdate(w http.ResponseWriter, r *http.Request) {
	projectID, err := routeID(r, "projectID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "invalid project form", http.StatusBadRequest)
		return
	}

	form := ProjectFormData{
		Action:      fmt.Sprintf("/admin/projects/%d", projectID),
		SubmitLabel: "Save project",
		Name:        r.FormValue("name"),
		Slug:        r.FormValue("slug"),
		Description: r.FormValue("description"),
	}

	params, validationError := projectFormInput(form.Name, form.Slug, form.Description)
	form.Name = params.Name
	form.Slug = params.Slug
	form.Description = params.Description
	if validationError != "" {
		form.Error = validationError
		h.renderProjectDetailPage(w, r, projectID, http.StatusBadRequest, form, ReleaseUploadData{})
		return
	}

	_, err = h.store.Projects.Update(r.Context(), projectID, db.UpdateProjectParams(params))
	if err != nil {
		switch {
		case errors.Is(err, db.ErrNotFound):
			http.NotFound(w, r)
		case errors.Is(err, db.ErrConflict):
			form.Error = "A project with that slug already exists."
			h.renderProjectDetailPage(w, r, projectID, http.StatusConflict, form, ReleaseUploadData{})
		default:
			http.Error(w, "project update failed", http.StatusInternalServerError)
		}
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/admin/projects/%d?status=project-updated", projectID), http.StatusSeeOther)
}

func (h *handler) adminProjectArchive(w http.ResponseWriter, r *http.Request) {
	projectID, err := routeID(r, "projectID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "invalid archive form", http.StatusBadRequest)
		return
	}

	state := r.FormValue("state")
	var (
		setActive  bool
		statusCode string
	)

	switch state {
	case "archive":
		setActive = false
		statusCode = "project-archived"
	case "restore":
		setActive = true
		statusCode = "project-restored"
	default:
		http.Error(w, "invalid archive action", http.StatusBadRequest)
		return
	}

	if err := h.store.Projects.SetActive(r.Context(), projectID, setActive); err != nil {
		if errors.Is(err, db.ErrNotFound) {
			http.NotFound(w, r)
			return
		}

		http.Error(w, "project archive failed", http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/admin/projects/%d?status=%s", projectID, statusCode), http.StatusSeeOther)
}

func (h *handler) adminProjectAttachTag(w http.ResponseWriter, r *http.Request) {
	projectID, err := routeID(r, "projectID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "invalid tag assignment form", http.StatusBadRequest)
		return
	}

	tagID, err := strconv.ParseInt(r.FormValue("tag_id"), 10, 64)
	if err != nil || tagID <= 0 {
		h.renderProjectDetailPageWithFlash(w, r, projectID, http.StatusBadRequest, "error", "Choose a valid tag to attach.")
		return
	}

	if _, err := h.store.Tags.GetByID(r.Context(), tagID); err != nil {
		if errors.Is(err, db.ErrNotFound) {
			h.renderProjectDetailPageWithFlash(w, r, projectID, http.StatusBadRequest, "error", "Selected tag no longer exists.")
			return
		}

		http.Error(w, "tag lookup failed", http.StatusInternalServerError)
		return
	}

	if err := h.store.Projects.AttachTag(r.Context(), projectID, tagID); err != nil {
		http.Error(w, "tag assignment failed", http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/admin/projects/%d?status=tag-attached", projectID), http.StatusSeeOther)
}

func (h *handler) adminProjectDetachTag(w http.ResponseWriter, r *http.Request) {
	projectID, err := routeID(r, "projectID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	tagID, err := routeID(r, "tagID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if err := h.store.Projects.DetachTag(r.Context(), projectID, tagID); err != nil {
		http.Error(w, "tag detach failed", http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/admin/projects/%d?status=tag-detached", projectID), http.StatusSeeOther)
}

func (h *handler) adminProjectUploadRelease(w http.ResponseWriter, r *http.Request) {
	projectID, err := routeID(r, "projectID")
	if err != nil {
		http.NotFound(w, r)
		return
	}

	r.Body = http.MaxBytesReader(w, r.Body, maxUploadRequestLimit(h.config.MaxUploadBytes))
	if err := r.ParseMultipartForm(16 << 20); err != nil {
		upload := ReleaseUploadData{
			Version:      r.FormValue("version"),
			Build:        r.FormValue("build"),
			ReleaseNotes: r.FormValue("release_notes"),
		}

		if isMaxBytesError(err) {
			upload.Error = fmt.Sprintf("Upload exceeds the configured %d MB limit.", maxUploadMegabytes(h.config.MaxUploadBytes))
		} else {
			upload.Error = "Upload form could not be read."
		}

		h.renderProjectDetailPage(w, r, projectID, http.StatusBadRequest, ProjectFormData{}, upload)
		return
	}
	defer func() {
		if r.MultipartForm != nil {
			_ = r.MultipartForm.RemoveAll()
		}
	}()

	upload := ReleaseUploadData{
		Version:      r.FormValue("version"),
		Build:        r.FormValue("build"),
		ReleaseNotes: r.FormValue("release_notes"),
	}

	file, header, err := r.FormFile("artifact")
	if err != nil {
		upload.Error = "Choose an artifact file to upload."
		h.renderProjectDetailPage(w, r, projectID, http.StatusBadRequest, ProjectFormData{}, upload)
		return
	}
	defer file.Close()

	if err := h.handleReleaseUpload(r, projectID, file, header, upload); err != nil {
		switch {
		case errors.Is(err, db.ErrNotFound):
			http.NotFound(w, r)
		case errors.Is(err, db.ErrConflict):
			upload.Error = "A release with that version and build already exists for this project."
			h.renderProjectDetailPage(w, r, projectID, http.StatusConflict, ProjectFormData{}, upload)
		default:
			upload.Error = err.Error()
			h.renderProjectDetailPage(w, r, projectID, http.StatusBadRequest, ProjectFormData{}, upload)
		}
		return
	}

	http.Redirect(w, r, fmt.Sprintf("/admin/projects/%d?status=release-uploaded", projectID), http.StatusSeeOther)
}

func (h *handler) handleReleaseUpload(r *http.Request, projectID int64, file multipart.File, header *multipart.FileHeader, upload ReleaseUploadData) error {
	_, err := h.releases.Upload(r.Context(), releases.UploadParams{
		ProjectID:        projectID,
		Version:          upload.Version,
		Build:            upload.Build,
		ReleaseNotes:     upload.ReleaseNotes,
		OriginalFilename: header.Filename,
		DeclaredType:     header.Header.Get("Content-Type"),
		Reader:           file,
		UploadedByUserID: h.currentUserID(r),
	})
	if err != nil {
		return err
	}

	return nil
}

func (h *handler) renderProjectFormPage(w http.ResponseWriter, r *http.Request, status int, form ProjectFormData) {
	data := ProjectFormPageData{
		PageData: h.basePageData(
			r,
			"New Project",
			"Protected Admin",
			"Create Project",
			"Add a new update stream with a stable slug so future releases and API access rules can target it reliably.",
		),
		Form: form,
	}

	renderPageStatus(w, h.renderer, "project_form", status, data)
}

func (h *handler) renderProjectDetailPage(w http.ResponseWriter, r *http.Request, projectID int64, status int, form ProjectFormData, upload ReleaseUploadData) {
	data, err := h.projectDetailPageData(r, projectID, form, upload)
	if err != nil {
		h.renderProjectDetailError(w, r, err)
		return
	}

	renderPageStatus(w, h.renderer, "project_detail", status, *data)
}

func (h *handler) renderProjectDetailPageWithFlash(w http.ResponseWriter, r *http.Request, projectID int64, status int, kind, message string) {
	data, err := h.projectDetailPageData(r, projectID, ProjectFormData{}, ReleaseUploadData{})
	if err != nil {
		h.renderProjectDetailError(w, r, err)
		return
	}

	data.Flash = &FlashMessage{Kind: kind, Message: message}
	renderPageStatus(w, h.renderer, "project_detail", status, *data)
}

func (h *handler) renderProjectDetailError(w http.ResponseWriter, r *http.Request, err error) {
	if errors.Is(err, db.ErrNotFound) {
		http.NotFound(w, r)
		return
	}

	http.Error(w, "project page failed", http.StatusInternalServerError)
}

func (h *handler) projectDetailPageData(r *http.Request, projectID int64, form ProjectFormData, upload ReleaseUploadData) (*ProjectDetailPageData, error) {
	project, err := h.store.Projects.GetByID(r.Context(), projectID)
	if err != nil {
		return nil, err
	}

	tags, err := h.store.Projects.ListTags(r.Context(), projectID)
	if err != nil {
		return nil, err
	}

	availableTags, err := h.store.Tags.ListAvailableForProject(r.Context(), projectID)
	if err != nil {
		return nil, err
	}

	releasesList, err := h.store.Releases.ListByProjectID(r.Context(), projectID)
	if err != nil {
		return nil, err
	}

	var latestRelease *db.Release
	if len(releasesList) > 0 {
		release := releasesList[0].Release
		latestRelease = &release
	}

	if form.Action == "" {
		form = ProjectFormData{
			Action:      fmt.Sprintf("/admin/projects/%d", projectID),
			SubmitLabel: "Save project",
			Name:        project.Name,
			Slug:        project.Slug,
			Description: project.Description,
			Error:       form.Error,
		}
	}

	if upload.Action == "" {
		upload.Action = fmt.Sprintf("/admin/projects/%d/releases", projectID)
	}
	if upload.MaxUploadMB == 0 {
		upload.MaxUploadMB = maxUploadMegabytes(h.config.MaxUploadBytes)
	}

	archiveState := "archive"
	archiveLabel := "Archive project"
	if !project.IsActive {
		archiveState = "restore"
		archiveLabel = "Restore project"
	}

	data := &ProjectDetailPageData{
		PageData: h.basePageData(
			r,
			project.Name,
			"Protected Admin",
			project.Name,
			"Edit project metadata, manage tag assignments, and upload releases with checksums stored in SQLite.",
		),
		Project:         *project,
		Form:            form,
		Tags:            tags,
		AvailableTags:   availableTags,
		Releases:        releasesList,
		LatestRelease:   latestRelease,
		AttachTagAction: fmt.Sprintf("/admin/projects/%d/tags", projectID),
		ArchiveAction:   fmt.Sprintf("/admin/projects/%d/archive", projectID),
		ArchiveState:    archiveState,
		ArchiveLabel:    archiveLabel,
		Upload:          upload,
	}

	return data, nil
}

func projectListFlash(code string) *FlashMessage {
	switch code {
	case "":
		return nil
	default:
		return nil
	}
}

func projectDetailFlash(code string) *FlashMessage {
	switch code {
	case "project-created":
		return &FlashMessage{Kind: "success", Message: "Project created. You can now attach tags and upload releases."}
	case "project-updated":
		return &FlashMessage{Kind: "success", Message: "Project details updated."}
	case "project-archived":
		return &FlashMessage{Kind: "success", Message: "Project archived."}
	case "project-restored":
		return &FlashMessage{Kind: "success", Message: "Project restored and active again."}
	case "tag-attached":
		return &FlashMessage{Kind: "success", Message: "Tag attached to the project."}
	case "tag-detached":
		return &FlashMessage{Kind: "success", Message: "Tag detached from the project."}
	case "release-uploaded":
		return &FlashMessage{Kind: "success", Message: "Release uploaded. Metadata, checksum, and storage path were saved."}
	default:
		return nil
	}
}
