package permissions

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// FolderAccessLevel represents the access level for folder operations.
// The zero value (FolderAccessView) is the most restrictive level.
type FolderAccessLevel int

const (
	// FolderAccessView grants read access to the folder and its contents.
	FolderAccessView FolderAccessLevel = iota
	// FolderAccessManage grants full control: read, edit, delete, and share management.
	// FolderAccessManage satisfies FolderAccessView (Manage > View).
	FolderAccessManage
)

// String returns the string representation of a FolderAccessLevel.
func (l FolderAccessLevel) String() string {
	switch l {
	case FolderAccessView:
		return "View"
	case FolderAccessManage:
		return "Manage"
	default:
		return "Unknown"
	}
}

// accessLevelSatisfies reports whether the granted level satisfies the required level.
// Since FolderAccessManage > FolderAccessView, Manage satisfies a View requirement.
func accessLevelSatisfies(granted, required FolderAccessLevel) bool {
	return granted >= required
}

// parseFolderAccessLevel converts a string access level (from FolderShare.AccessLevel) to
// a FolderAccessLevel constant. Defaults to FolderAccessView for unknown values.
func parseFolderAccessLevel(s string) FolderAccessLevel {
	switch strings.ToLower(s) {
	case "manage":
		return FolderAccessManage
	case "view":
		return FolderAccessView
	default:
		return FolderAccessView
	}
}

// FolderAccessService checks folder-level access using FolderShare metadata.
//
// Resolution delegates group membership and role hierarchy queries to the
// provided PermissionService — no duplication of resolution logic.
//
// Access resolution order:
//  1. SuperUser / ModifyAllRecords → always true
//  2. ViewAllRecords → true for View only
//  3. Folder.CreatedById == user.Id → true for all levels (owner)
//  4. Scan ALL FolderShare rows, compute the maximum granted access level across
//     all matching shares (User, Role, RoleAndSubordinates, Group, Organization),
//     then check if max >= required. This ensures additive grants work correctly.
//  5. Default: deny
type FolderAccessService struct {
	ps *PermissionService
	db *sql.DB
}

// NewFolderAccessService creates a new FolderAccessService.
// ps is used for system permission checks and role/group resolution.
// db is used for direct FolderShare and ReportFolder queries.
func NewFolderAccessService(ps *PermissionService, db *sql.DB) *FolderAccessService {
	return &FolderAccessService{ps: ps, db: db}
}

// CheckAccess reports whether the user has at least the required access level on
// the folder identified by folderID.
//
// It fetches the folder row once, then evaluates each access layer in order.
// Returns false on any database error (fail-closed).
func (fas *FolderAccessService) CheckAccess(ctx context.Context, user *models.UserSession, folderID string, required FolderAccessLevel) bool {
	if user == nil {
		return false
	}

	// Layer 1: SuperUser / ModifyAllRecords → full access
	if user.IsSystemAdmin || constants.IsSuperUser(user.ProfileID) {
		return true
	}

	// Check object-level permissions for ModifyAllRecords / ViewAllRecords.
	// We re-use the existing object permission loader via PermissionService.
	objPerm, err := fas.ps.loadEffectiveObjectPermission(ctx, user, "ReportFolder")
	if err == nil && objPerm != nil {
		if objPerm.PermissionsModifyAllRecords {
			return true
		}
		if objPerm.PermissionsViewAllRecords && required == FolderAccessView {
			return true
		}
	}

	// Layer 3: Ownership — fetch OwnerId (falling back to CreatedById for existing folders)
	var ownerID string
	err = fas.db.QueryRowContext(ctx,
		"SELECT COALESCE(NULLIF(`OwnerId`, ''), `CreatedById`) FROM `ReportFolder` WHERE `Id` = ? AND `IsDeleted` = 0",
		folderID,
	).Scan(&ownerID)
	if err != nil {
		// Folder not found or DB error — deny
		return false
	}
	if ownerID == user.ID {
		return true
	}

	// Fetch all FolderShare rows for this folder
	shares, err := fas.loadFolderShares(ctx, folderID)
	if err != nil {
		return false
	}

	// Pre-compute role descendants (for RoleAndSubordinates checks)
	var roleDescendantIDs []string
	if user.RoleID != nil && *user.RoleID != "" {
		roleDescendantIDs = fas.ps.GetRoleDescendants(*user.RoleID)
	}

	// Pre-compute group IDs
	groupIDs, err := fas.ps.repo.GetUserGroupIDs(ctx, user.ID)
	if err != nil {
		groupIDs = nil // non-fatal; continue without group matching
	}

	// Scan ALL matching shares and compute the maximum granted access level.
	// This ensures additive grants work correctly regardless of DB row ordering —
	// a user with both a View share and a Manage share gets Manage access.
	var maxGranted FolderAccessLevel = -1 // sentinel: no matching share found yet

	for _, share := range shares {
		granted := parseFolderAccessLevel(share.accessLevel)

		var matches bool
		switch share.sharedToType {
		case "User":
			matches = share.sharedToID == user.ID

		case "Role":
			matches = user.RoleID != nil && share.sharedToID == *user.RoleID

		case "RoleAndSubordinates":
			// The share covers the named role AND all roles below it.
			// The user qualifies if their role IS the named role, or if the named
			// role is an ancestor of the user's role (user is a subordinate).
			if user.RoleID != nil {
				if share.sharedToID == *user.RoleID {
					matches = true
				} else {
					ancestors := fas.ps.GetRoleAncestors(*user.RoleID)
					for _, ancestorID := range ancestors {
						if ancestorID == share.sharedToID {
							matches = true
							break
						}
					}
				}
			}

		case "Group":
			for _, gid := range groupIDs {
				if gid == share.sharedToID {
					matches = true
					break
				}
			}

		case "Organization":
			matches = true
		}

		if matches && granted > maxGranted {
			maxGranted = granted
		}
	}

	_ = roleDescendantIDs // pre-computed for VisibleFolderIDs SQL; not needed here

	// After scanning all shares: check if the maximum grant satisfies the requirement.
	if maxGranted >= 0 && accessLevelSatisfies(maxGranted, required) {
		return true
	}

	// Default deny
	return false
}

// folderShare is an internal value type for a single FolderShare row.
type folderShare struct {
	sharedToID   string
	sharedToType string
	accessLevel  string
}

// loadFolderShares fetches all non-deleted FolderShare rows for the given folder.
func (fas *FolderAccessService) loadFolderShares(ctx context.Context, folderID string) ([]folderShare, error) {
	rows, err := fas.db.QueryContext(ctx,
		"SELECT `SharedToId`, `SharedToType`, `AccessLevel` FROM `FolderShare` WHERE `FolderId` = ?",
		folderID,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var shares []folderShare
	for rows.Next() {
		var s folderShare
		if err := rows.Scan(&s.sharedToID, &s.sharedToType, &s.accessLevel); err != nil {
			return nil, err
		}
		shares = append(shares, s)
	}
	return shares, rows.Err()
}

// FolderShareRow represents a row from the FolderShare table.
type FolderShareRow struct {
	ID               string
	FolderID         string
	SharedToID       string
	SharedToType     string
	AccessLevel      string
	CreatedByID      string
	CreatedDate      time.Time
	LastModifiedDate time.Time
}

// GetFolderType returns the Type field of a folder ("Report" or "Dashboard").
func (fas *FolderAccessService) GetFolderType(ctx context.Context, folderID string) string {
	var folderType string
	fas.db.QueryRowContext(ctx,
		"SELECT `Type` FROM `ReportFolder` WHERE `Id` = ? AND `IsDeleted` = 0", folderID,
	).Scan(&folderType)
	return folderType
}

// InsertShare creates a FolderShare row. Authorization (Manage access) must be checked by the caller.
func (fas *FolderAccessService) InsertShare(ctx context.Context, folderID, sharedToID, sharedToType, accessLevel, createdByID string) (string, error) {
	id := uuid.New().String()
	_, err := fas.db.ExecContext(ctx,
		"INSERT INTO `FolderShare` (`Id`, `FolderId`, `SharedToId`, `SharedToType`, `AccessLevel`, `CreatedById`, `CreatedDate`, `LastModifiedDate`) VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())",
		id, folderID, sharedToID, sharedToType, accessLevel, createdByID,
	)
	return id, err
}

// HasShareByType reports whether a folder already has at least one share row
// for the given SharedToType.
func (fas *FolderAccessService) HasShareByType(ctx context.Context, folderID, sharedToType string) (bool, error) {
	var count int
	err := fas.db.QueryRowContext(ctx,
		"SELECT COUNT(*) FROM `FolderShare` WHERE `FolderId` = ? AND `SharedToType` = ?",
		folderID, sharedToType,
	).Scan(&count)
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

// EnsureOrganizationViewShare guarantees the standard organization-wide View share
// used by Public folders without duplicating existing Organization shares.
func (fas *FolderAccessService) EnsureOrganizationViewShare(ctx context.Context, folderID, createdByID string) error {
	hasOrgShare, err := fas.HasShareByType(ctx, folderID, "Organization")
	if err != nil {
		return err
	}
	if hasOrgShare {
		return nil
	}
	_, err = fas.InsertShare(ctx, folderID, "ALL_INTERNAL", "Organization", "View", createdByID)
	return err
}

// ReplaceAllShares atomically replaces all shares for a folder within a transaction.
// Prevents the half-deleted state that occurs with separate DeleteAllShares + InsertShare calls.
type ShareInput struct {
	SharedToID   string
	SharedToType string
	AccessLevel  string
}

func (fas *FolderAccessService) ReplaceAllShares(ctx context.Context, folderID, createdByID string, shares []ShareInput) ([]string, error) {
	tx, err := fas.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	// Delete all existing
	if _, err := tx.ExecContext(ctx, "DELETE FROM `FolderShare` WHERE `FolderId` = ?", folderID); err != nil {
		return nil, err
	}

	// Insert new
	ids := make([]string, 0, len(shares))
	for _, s := range shares {
		id := uuid.New().String()
		if _, err := tx.ExecContext(ctx,
			"INSERT INTO `FolderShare` (`Id`, `FolderId`, `SharedToId`, `SharedToType`, `AccessLevel`, `CreatedById`, `CreatedDate`, `LastModifiedDate`) VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())",
			id, folderID, s.SharedToID, s.SharedToType, s.AccessLevel, createdByID,
		); err != nil {
			return nil, err
		}
		ids = append(ids, id)
	}

	if err := tx.Commit(); err != nil {
		return nil, err
	}
	return ids, nil
}

// DeleteShare removes a FolderShare row by ID.
func (fas *FolderAccessService) DeleteShare(ctx context.Context, shareID string) error {
	_, err := fas.db.ExecContext(ctx, "DELETE FROM `FolderShare` WHERE `Id` = ?", shareID)
	return err
}

// DeleteSharesByType removes FolderShare rows for a folder with a specific SharedToType.
// Use this for targeted removal (e.g., removing Organization shares when going Hidden)
// without destroying User/Group/Role-specific shares.
func (fas *FolderAccessService) DeleteSharesByType(ctx context.Context, folderID, sharedToType string) error {
	_, err := fas.db.ExecContext(ctx, "DELETE FROM `FolderShare` WHERE `FolderId` = ? AND `SharedToType` = ?", folderID, sharedToType)
	return err
}

// DeleteAllShares removes all FolderShare rows for a folder.
func (fas *FolderAccessService) DeleteAllShares(ctx context.Context, folderID string) error {
	_, err := fas.db.ExecContext(ctx, "DELETE FROM `FolderShare` WHERE `FolderId` = ?", folderID)
	return err
}

// UpdateShareAccessLevel updates the AccessLevel of a single share.
func (fas *FolderAccessService) UpdateShareAccessLevel(ctx context.Context, shareID, accessLevel string) error {
	_, err := fas.db.ExecContext(ctx,
		"UPDATE `FolderShare` SET `AccessLevel` = ?, `LastModifiedDate` = NOW() WHERE `Id` = ?",
		accessLevel, shareID,
	)
	return err
}

// GetShare retrieves a single FolderShare by ID.
func (fas *FolderAccessService) GetShare(ctx context.Context, shareID string) (*FolderShareRow, error) {
	var s FolderShareRow
	err := fas.db.QueryRowContext(ctx,
		"SELECT `Id`, `FolderId`, `SharedToId`, `SharedToType`, `AccessLevel`, `CreatedById`, `CreatedDate`, `LastModifiedDate` FROM `FolderShare` WHERE `Id` = ?",
		shareID,
	).Scan(&s.ID, &s.FolderID, &s.SharedToID, &s.SharedToType, &s.AccessLevel, &s.CreatedByID, &s.CreatedDate, &s.LastModifiedDate)
	if err != nil {
		return nil, err
	}
	return &s, nil
}

// ListShares retrieves all FolderShare rows for a folder.
func (fas *FolderAccessService) ListShares(ctx context.Context, folderID string) ([]FolderShareRow, error) {
	rows, err := fas.db.QueryContext(ctx,
		"SELECT `Id`, `FolderId`, `SharedToId`, `SharedToType`, `AccessLevel`, `CreatedById`, `CreatedDate`, `LastModifiedDate` FROM `FolderShare` WHERE `FolderId` = ?",
		folderID,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var shares []FolderShareRow
	for rows.Next() {
		var s FolderShareRow
		if err := rows.Scan(&s.ID, &s.FolderID, &s.SharedToID, &s.SharedToType, &s.AccessLevel, &s.CreatedByID, &s.CreatedDate, &s.LastModifiedDate); err != nil {
			return nil, err
		}
		shares = append(shares, s)
	}
	return shares, rows.Err()
}

// TransferFolderOwnership reassigns all ReportFolder rows owned by sourceUserID
// to targetUserID. Called by admins when deactivating a user to prevent orphaned
// reports and dashboards.
func (fas *FolderAccessService) TransferFolderOwnership(ctx context.Context, sourceUserID, targetUserID string) error {
	_, err := fas.db.ExecContext(ctx,
		"UPDATE `ReportFolder` SET `OwnerId` = ?, `LastModifiedById` = ?, `LastModifiedDate` = NOW() WHERE (COALESCE(NULLIF(`OwnerId`, ''), `CreatedById`) = ?) AND (`IsDeleted` = 0 OR `IsDeleted` IS NULL)",
		targetUserID, targetUserID, sourceUserID,
	)
	return err
}

// VisibleFolderIDs returns the set of ReportFolder IDs the user can access at
// FolderAccessView level or higher.
//
// It pre-computes role descendant IDs and group IDs in Go, then issues a single
// parameterized SQL query to avoid N+1 lookups.
//
// SuperUser and ViewAllRecords/ModifyAllRecords users receive nil (caller should
// treat nil as "all folders visible").
func (fas *FolderAccessService) VisibleFolderIDs(ctx context.Context, user *models.UserSession) ([]string, error) {
	if user == nil {
		return []string{}, nil
	}

	// SuperUser or ModifyAllRecords → all folders visible; return nil sentinel
	if user.IsSystemAdmin || constants.IsSuperUser(user.ProfileID) {
		return nil, nil
	}

	// RunReports system permission gate — folders are part of the analytics subsystem.
	// The REST /folders route enforces RequirePermission(PermRunReports), but the SOQL
	// /query path bypasses route guards. Enforce here for consistency so that a user
	// without RunReports cannot enumerate folder metadata via SOQL.
	if !fas.ps.CheckSystemPermission(ctx, user, string(constants.PermRunReports)) {
		return []string{}, nil
	}

	// Check object-level ModifyAllRecords / ViewAllRecords
	objPerm, err := fas.ps.loadEffectiveObjectPermission(ctx, user, "ReportFolder")
	if err == nil && objPerm != nil {
		if objPerm.PermissionsModifyAllRecords || objPerm.PermissionsViewAllRecords {
			return nil, nil
		}
	}

	// Pre-compute role descendants: for RoleAndSubordinates, the shared-to role is
	// an ancestor of the user's role — equivalently, the user's role is a descendant
	// of the shared-to role. GetRoleDescendants(sharedToRole) would give us the
	// matching roles, but we need it from the user side: collect all ancestor role IDs.
	var userRoleID string
	if user.RoleID != nil {
		userRoleID = *user.RoleID
	}

	// ancestorAndSelfRoleIDs: all role IDs that, if used as SharedToId on a
	// RoleAndSubordinates share, would cover this user.
	var ancestorAndSelfRoleIDs []string
	if userRoleID != "" {
		ancestorAndSelfRoleIDs = append([]string{userRoleID}, fas.ps.GetRoleAncestors(userRoleID)...)
	}

	// Pre-compute group IDs
	groupIDs, err := fas.ps.repo.GetUserGroupIDs(ctx, user.ID)
	if err != nil {
		groupIDs = nil
	}

	// Build the SQL query dynamically based on available IDs.
	// Base conditions always present: owner + direct user share + Organization share.
	conditions := []string{
		"COALESCE(NULLIF(rf.`OwnerId`, ''), rf.`CreatedById`) = ?",
		"(fs.`SharedToType` = 'User' AND fs.`SharedToId` = ?)",
		"fs.`SharedToType` = 'Organization'",
	}
	args := []interface{}{user.ID, user.ID}

	// Role share: user's role matches SharedToType=Role
	if userRoleID != "" {
		conditions = append(conditions, "(fs.`SharedToType` = 'Role' AND fs.`SharedToId` = ?)")
		args = append(args, userRoleID)
	}

	// RoleAndSubordinates: shared-to role is user's role or any ancestor of user's role
	if len(ancestorAndSelfRoleIDs) > 0 {
		placeholders := make([]string, len(ancestorAndSelfRoleIDs))
		for i := range placeholders {
			placeholders[i] = "?"
		}
		conditions = append(conditions,
			fmt.Sprintf("(fs.`SharedToType` = 'RoleAndSubordinates' AND fs.`SharedToId` IN (%s))",
				strings.Join(placeholders, ", ")),
		)
		for _, id := range ancestorAndSelfRoleIDs {
			args = append(args, id)
		}
	}

	// Group share
	if len(groupIDs) > 0 {
		placeholders := make([]string, len(groupIDs))
		for i := range placeholders {
			placeholders[i] = "?"
		}
		conditions = append(conditions,
			fmt.Sprintf("(fs.`SharedToType` = 'Group' AND fs.`SharedToId` IN (%s))",
				strings.Join(placeholders, ", ")),
		)
		for _, id := range groupIDs {
			args = append(args, id)
		}
	}

	querySQL := fmt.Sprintf(`
SELECT DISTINCT rf.`+"`Id`"+`
FROM `+"`ReportFolder`"+` rf
LEFT JOIN `+"`FolderShare`"+` fs ON rf.`+"`Id`"+` = fs.`+"`FolderId`"+`
WHERE rf.`+"`IsDeleted`"+` = 0 AND (%s)`,
		strings.Join(conditions, "\n    OR "),
	)

	rows, err := fas.db.QueryContext(ctx, querySQL, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var ids []string
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			return nil, err
		}
		ids = append(ids, id)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}

	return ids, nil
}
