package lib

import (
	"errors"
	"strings"
)

var (
	ErrAccessDenied    = errors.New("access denied")
	ErrInsufficientOps = errors.New("insufficient permissions")
)

// CheckAccess verifies that actorID has the required operation on the given project/entry.
// op is one of "r", "w", "d", "m" (read, write, delete, manage).
// Returns the matching Access grant or an error.
func CheckAccess(db *DB, actorID, projectID string, workstreamID string, op string) (*Access, error) {
	grants, err := getUserAccess(db, actorID, projectID)
	if err != nil {
		return nil, err
	}

	for _, g := range grants {
		if g.RevokedAt != nil {
			continue
		}
		// Grant matches if it covers all workstreams or the specific one
		if g.WorkstreamID != "" && g.WorkstreamID != workstreamID && workstreamID != "" {
			continue
		}
		if strings.Contains(g.Ops, op) {
			return &g, nil
		}
	}

	return nil, ErrAccessDenied
}

// CheckAccessRead is a convenience for read operations.
func CheckAccessRead(db *DB, actorID, projectID, workstreamID string) error {
	_, err := CheckAccess(db, actorID, projectID, workstreamID, "r")
	return err
}

// CheckAccessWrite is a convenience for write operations.
func CheckAccessWrite(db *DB, actorID, projectID, workstreamID string) error {
	_, err := CheckAccess(db, actorID, projectID, workstreamID, "w")
	return err
}

// CheckAccessDelete is a convenience for delete operations.
func CheckAccessDelete(db *DB, actorID, projectID, workstreamID string) error {
	_, err := CheckAccess(db, actorID, projectID, workstreamID, "d")
	return err
}

// IsSuperAdmin returns true if the user has super_admin role on any project.
func IsSuperAdmin(db *DB, userID string) (bool, error) {
	var count int
	err := db.Conn.QueryRow(
		`SELECT COUNT(*) FROM access WHERE user_id = ? AND role = ? AND revoked_at IS NULL`,
		userID, RoleSuperAdmin,
	).Scan(&count)
	return count > 0, err
}

// IsBuyerRole returns true if the role is a buyer role.
// Buyers cannot see pre_dataroom entries.
func IsBuyerRole(role string) bool {
	return role == RoleBuyerAdmin || role == RoleBuyerMember
}

// CanGrantRole checks whether the granting role can grant the target role.
// A role can only grant roles at or below its own hierarchy level.
func CanGrantRole(granterRole, targetRole string) bool {
	granterLevel, ok1 := RoleHierarchy[granterRole]
	targetLevel, ok2 := RoleHierarchy[targetRole]
	if !ok1 || !ok2 {
		return false
	}
	return granterLevel >= targetLevel
}

// ResolveWorkstreamID walks up the entry tree to find the workstream ancestor.
// Returns empty string if the entry is at project level (depth 0).
func ResolveWorkstreamID(db *DB, entry *Entry) (string, error) {
	if entry.Type == TypeWorkstream {
		return entry.EntryID, nil
	}
	if entry.Type == TypeProject || entry.Depth == 0 {
		return "", nil
	}

	// Walk up to find workstream
	current := entry
	for current.Depth > 1 && current.ParentID != "" {
		parent, err := entryReadSystem(db, current.ParentID)
		if err != nil {
			return "", err
		}
		if parent == nil {
			return "", nil
		}
		if parent.Type == TypeWorkstream {
			return parent.EntryID, nil
		}
		current = parent
	}
	return current.EntryID, nil
}

// getUserAccess retrieves all active access grants for a user on a project.
func getUserAccess(db *DB, userID, projectID string) ([]Access, error) {
	rows, err := db.Conn.Query(
		`SELECT id, project_id, workstream_id, user_id, role, ops, can_grant,
		        granted_by, granted_at, revoked_at, revoked_by
		 FROM access
		 WHERE user_id = ? AND project_id = ? AND revoked_at IS NULL`,
		userID, projectID,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var grants []Access
	for rows.Next() {
		var a Access
		var wsID *string
		var revokedAt *int64
		var revokedBy *string
		err := rows.Scan(&a.ID, &a.ProjectID, &wsID, &a.UserID, &a.Role,
			&a.Ops, &a.CanGrant, &a.GrantedBy, &a.GrantedAt, &revokedAt, &revokedBy)
		if err != nil {
			return nil, err
		}
		if wsID != nil {
			a.WorkstreamID = *wsID
		}
		a.RevokedAt = revokedAt
		if revokedBy != nil {
			a.RevokedBy = *revokedBy
		}
		grants = append(grants, a)
	}
	return grants, rows.Err()
}

// GetUserHighestRole returns the highest-privilege role a user has on a project.
func GetUserHighestRole(db *DB, userID, projectID string) (string, error) {
	grants, err := getUserAccess(db, userID, projectID)
	if err != nil {
		return "", err
	}

	bestRole := ""
	bestLevel := -1
	for _, g := range grants {
		if level, ok := RoleHierarchy[g.Role]; ok && level > bestLevel {
			bestLevel = level
			bestRole = g.Role
		}
	}
	if bestRole == "" {
		return "", ErrAccessDenied
	}
	return bestRole, nil
}
