// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package handler

import (
	"context"

	"code.vikunja.io/api/pkg/db"
	"code.vikunja.io/api/pkg/events"
	"code.vikunja.io/api/pkg/log"
	"code.vikunja.io/api/pkg/web"
)

// DoCreate runs the permission check + model Create + commit pipeline for a
// CObject. Framework-agnostic: callable from both Echo (CreateWeb) and Huma.
// Caller is responsible for body/path binding and validation before calling.
func DoCreate(_ context.Context, obj CObject, a web.Auth) error {
	s := db.NewSession()
	defer func() {
		if err := s.Close(); err != nil {
			log.Errorf("Could not close session: %s", err)
		}
	}()

	canCreate, err := obj.CanCreate(s, a)
	if err != nil {
		_ = s.Rollback()
		events.CleanupPending(s)
		return err
	}
	if !canCreate {
		_ = s.Rollback()
		events.CleanupPending(s)
		log.Warningf("Tried to create while not having the permissions for it (User: %v)", a)
		return ErrGenericForbidden{}
	}

	if err := obj.Create(s, a); err != nil {
		_ = s.Rollback()
		events.CleanupPending(s)
		return err
	}

	if err := s.Commit(); err != nil {
		events.CleanupPending(s)
		return err
	}

	events.DispatchPending(s)
	return nil
}

// DoReadOne runs the permission check + model ReadOne + commit pipeline for a
// CObject. obj should have its identifying fields set before call. On success,
// obj is fully populated. maxPermission is exposed via the x-max-permission
// header in the Echo wrapper; Huma wrapper may ignore it.
func DoReadOne(_ context.Context, obj CObject, a web.Auth) (maxPermission int, err error) {
	s := db.NewSession()
	defer func() {
		if cerr := s.Close(); cerr != nil {
			log.Errorf("Could not close session: %s", cerr)
		}
	}()

	canRead, maxPermission, err := obj.CanRead(s, a)
	if err != nil {
		_ = s.Rollback()
		events.CleanupPending(s)
		return 0, err
	}
	if !canRead {
		_ = s.Rollback()
		events.CleanupPending(s)
		log.Warningf("Tried to read while not having the permissions for it (User: %v)", a)
		return 0, ErrGenericForbidden{Message: "You don't have the permission to see this"}
	}

	if err := obj.ReadOne(s, a); err != nil {
		_ = s.Rollback()
		events.CleanupPending(s)
		return 0, err
	}

	if err := s.Commit(); err != nil {
		events.CleanupPending(s)
		return 0, err
	}

	events.DispatchPending(s)
	return maxPermission, nil
}

// DoReadAll runs the ReadAll + commit pipeline for a CObject. obj may carry
// scoping context (e.g., TaskID on LabelTask). Returns the result slice/
// interface, the result count, and total count. Pagination header math and
// nil-slice normalization remain the caller's responsibility.
func DoReadAll(_ context.Context, obj CObject, a web.Auth, search string, page, perPage int) (result any, resultCount int, total int64, err error) {
	s := db.NewSession()
	defer func() {
		if cerr := s.Close(); cerr != nil {
			log.Errorf("Could not close session: %s", cerr)
		}
	}()

	result, resultCount, total, err = obj.ReadAll(s, a, search, page, perPage)
	if err != nil {
		_ = s.Rollback()
		events.CleanupPending(s)
		return nil, 0, 0, err
	}

	if err = s.Commit(); err != nil {
		events.CleanupPending(s)
		return nil, 0, 0, err
	}

	events.DispatchPending(s)
	return result, resultCount, total, nil
}

// DoUpdate runs the permission check + model Update + commit pipeline for a
// CObject. Framework-agnostic. Caller is responsible for body/path binding
// and validation before calling.
func DoUpdate(_ context.Context, obj CObject, a web.Auth) error {
	s := db.NewSession()
	defer func() {
		if err := s.Close(); err != nil {
			log.Errorf("Could not close session: %s", err)
		}
	}()

	canUpdate, err := obj.CanUpdate(s, a)
	if err != nil {
		_ = s.Rollback()
		events.CleanupPending(s)
		return err
	}
	if !canUpdate {
		_ = s.Rollback()
		events.CleanupPending(s)
		log.Warningf("Tried to update while not having the permissions for it (User: %v)", a)
		return ErrGenericForbidden{}
	}

	if err := obj.Update(s, a); err != nil {
		_ = s.Rollback()
		events.CleanupPending(s)
		return err
	}

	if err := s.Commit(); err != nil {
		events.CleanupPending(s)
		return err
	}

	events.DispatchPending(s)
	return nil
}

// DoDelete runs the permission check + model Delete + commit pipeline for a
// CObject. Framework-agnostic. Caller is responsible for path binding before
// calling.
func DoDelete(_ context.Context, obj CObject, a web.Auth) error {
	s := db.NewSession()
	defer func() {
		if err := s.Close(); err != nil {
			log.Errorf("Could not close session: %s", err)
		}
	}()

	canDelete, err := obj.CanDelete(s, a)
	if err != nil {
		_ = s.Rollback()
		events.CleanupPending(s)
		return err
	}
	if !canDelete {
		_ = s.Rollback()
		events.CleanupPending(s)
		log.Warningf("Tried to delete while not having the permissions for it (User: %v)", a)
		return ErrGenericForbidden{}
	}

	if err := obj.Delete(s, a); err != nil {
		_ = s.Rollback()
		events.CleanupPending(s)
		return err
	}

	if err := s.Commit(); err != nil {
		events.CleanupPending(s)
		return err
	}

	events.DispatchPending(s)
	return nil
}
