package organization

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

type organizationMemberRemovalTarget struct {
	MemberID   string
	UserID     string
	Role       string
	Status     string
	DisabledAt *time.Time
}

// PreviewOrganizationMemberRemovalImpactForUser returns the public confirmation
// data for removing an organization member. It is read-only and intentionally
// shares the same guard calculation as the mutating remove path.
func (store *Store) PreviewOrganizationMemberRemovalImpactForUser(ctx context.Context, userID string, organizationID string, memberID string) (OrganizationMemberRemovalImpact, error) {
	var impact OrganizationMemberRemovalImpact
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var err error
		impact, err = store.InTx(tx).PreviewOrganizationMemberRemovalImpactForUser(ctx, userID, organizationID, memberID)
		return err
	})
	if err != nil {
		return OrganizationMemberRemovalImpact{}, err
	}
	return impact, nil
}

func (txs TxStore) PreviewOrganizationMemberRemovalImpactForUser(ctx context.Context, userID string, organizationID string, memberID string) (OrganizationMemberRemovalImpact, error) {
	trimmedOrganizationID := strings.TrimSpace(organizationID)
	trimmedMemberID := strings.TrimSpace(memberID)
	if trimmedOrganizationID == "" || trimmedMemberID == "" {
		return OrganizationMemberRemovalImpact{}, badRequest("Invalid request")
	}
	if _, err := txs.requireActiveOrganizationAdmin(ctx, txs.db, userID, trimmedOrganizationID); err != nil {
		return OrganizationMemberRemovalImpact{}, err
	}
	member, err := txs.loadOrganizationMemberView(ctx, txs.db, trimmedOrganizationID, trimmedMemberID)
	if err != nil {
		return OrganizationMemberRemovalImpact{}, err
	}
	return txs.buildOrganizationMemberRemovalImpact(ctx, txs.db, trimmedOrganizationID, userID, member)
}

// RemoveOrganizationMemberForUser soft-removes an organization member and
// revokes future organization-derived action paths without deleting history.
func (store *Store) RemoveOrganizationMemberForUser(ctx context.Context, userID string, organizationID string, memberID string) (OrganizationMemberRemovalResult, error) {
	var result OrganizationMemberRemovalResult
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var err error
		result, err = store.InTx(tx).RemoveOrganizationMemberForUser(ctx, userID, organizationID, memberID)
		return err
	})
	if err != nil {
		return OrganizationMemberRemovalResult{}, err
	}
	return result, nil
}

func (txs TxStore) RemoveOrganizationMemberForUser(ctx context.Context, userID string, organizationID string, memberID string) (OrganizationMemberRemovalResult, error) {
	trimmedOrganizationID := strings.TrimSpace(organizationID)
	trimmedMemberID := strings.TrimSpace(memberID)
	if trimmedOrganizationID == "" || trimmedMemberID == "" {
		return OrganizationMemberRemovalResult{}, badRequest("Invalid request")
	}
	return withTxValue(ctx, txs.db, func(tx coredb.Tx) (OrganizationMemberRemovalResult, error) {
		if _, err := txs.requireActiveOrganizationAdmin(ctx, tx, userID, trimmedOrganizationID); err != nil {
			return OrganizationMemberRemovalResult{}, err
		}
		target, err := txs.loadOrganizationMemberRemovalTarget(ctx, tx, trimmedOrganizationID, trimmedMemberID, true)
		if err != nil {
			return OrganizationMemberRemovalResult{}, err
		}
		if target.Status != organizationMemberStatusActive {
			return OrganizationMemberRemovalResult{
				MemberID:   target.MemberID,
				Status:     target.Status,
				DisabledAt: target.DisabledAt,
			}, nil
		}

		member, err := txs.loadOrganizationMemberView(ctx, tx, trimmedOrganizationID, trimmedMemberID)
		if err != nil {
			return OrganizationMemberRemovalResult{}, err
		}
		impact, err := txs.buildOrganizationMemberRemovalImpact(ctx, tx, trimmedOrganizationID, userID, member)
		if err != nil {
			return OrganizationMemberRemovalResult{}, err
		}
		if len(impact.BlockingReasons) > 0 {
			return OrganizationMemberRemovalResult{}, conflict(impact.BlockingReasons[0].Code)
		}

		workspaceIDs, err := txs.listOrganizationMemberRemovalWorkspaceIDs(ctx, tx, trimmedOrganizationID, trimmedMemberID, target.UserID)
		if err != nil {
			return OrganizationMemberRemovalResult{}, err
		}
		now := time.Now().UTC()
		if err := txs.deactivateOrganizationMemberAccessRows(ctx, tx, trimmedOrganizationID, trimmedMemberID, now); err != nil {
			return OrganizationMemberRemovalResult{}, err
		}
		commandTag, err := tx.Exec(ctx, `
UPDATE organization_members
SET status = 'inactive',
    disabled_at = $3,
    updated_at = $3
WHERE organization_id = $1
  AND id = $2
  AND status = 'active'
`, trimmedOrganizationID, trimmedMemberID, now)
		if err != nil {
			return OrganizationMemberRemovalResult{}, fmt.Errorf("soft remove organization member: %w", err)
		}
		if commandTag.RowsAffected() == 0 {
			return OrganizationMemberRemovalResult{}, ErrNotFound
		}

		for _, workspaceID := range workspaceIDs {
			if _, err := txs.core.ArchiveWorkspaceMemberRuntimeResourcesInTx(ctx, tx, workspaceID, target.UserID); err != nil {
				return OrganizationMemberRemovalResult{}, err
			}
			if _, err := txs.core.DeactivateHumanWorkspaceMemberInTx(ctx, tx, workspaceID, target.UserID); err != nil && !errors.Is(err, ErrNotFound) {
				return OrganizationMemberRemovalResult{}, err
			}
			if _, _, _, err := txs.reconcileOrganizationMemberWorkspaceProjection(ctx, tx, trimmedOrganizationID, workspaceID, trimmedMemberID); err != nil {
				return OrganizationMemberRemovalResult{}, err
			}
		}

		return OrganizationMemberRemovalResult{
			MemberID:   trimmedMemberID,
			Status:     "inactive",
			DisabledAt: &now,
		}, nil
	})
}

func (txs TxStore) buildOrganizationMemberRemovalImpact(ctx context.Context, queryer queryer, organizationID string, actorUserID string, member OrganizationMemberView) (OrganizationMemberRemovalImpact, error) {
	workspaceAccessesByMemberID, err := txs.listOrganizationMemberWorkspaceAccessesForMemberIDs(ctx, organizationID, []string{member.ID})
	if err != nil {
		return OrganizationMemberRemovalImpact{}, err
	}
	affectedWorkspaces := make([]OrganizationMemberRemovalWorkspace, 0, len(workspaceAccessesByMemberID[member.ID]))
	for _, access := range workspaceAccessesByMemberID[member.ID] {
		affectedWorkspaces = append(affectedWorkspaces, OrganizationMemberRemovalWorkspace{
			ID:      access.WorkspaceID,
			Name:    access.WorkspaceName,
			Slug:    access.WorkspaceSlug,
			Role:    access.Role,
			Sources: append([]string(nil), access.Sources...),
		})
	}

	blockers, err := txs.listOrganizationMemberRemovalBlockers(ctx, queryer, organizationID, actorUserID, member)
	if err != nil {
		return OrganizationMemberRemovalImpact{}, err
	}
	agents, err := txs.listOrganizationMemberRemovalAgents(ctx, queryer, organizationID, member.UserID)
	if err != nil {
		return OrganizationMemberRemovalImpact{}, err
	}
	machines, err := txs.listOrganizationMemberRemovalMachines(ctx, queryer, organizationID, member.UserID)
	if err != nil {
		return OrganizationMemberRemovalImpact{}, err
	}

	member.WorkspaceAccesses = nil
	member.WorkspaceAccessCount = len(affectedWorkspaces)
	return OrganizationMemberRemovalImpact{
		Member:             member,
		BlockingReasons:    blockers,
		AffectedWorkspaces: affectedWorkspaces,
		AgentsToDisable:    agents,
		MachinesToRevoke:   machines,
	}, nil
}

func (txs TxStore) listOrganizationMemberRemovalBlockers(ctx context.Context, queryer queryer, organizationID string, actorUserID string, member OrganizationMemberView) ([]OrganizationMemberRemovalBlocker, error) {
	blockers := make([]OrganizationMemberRemovalBlocker, 0, 4)
	if strings.TrimSpace(actorUserID) == strings.TrimSpace(member.UserID) && member.Status == organizationMemberStatusActive {
		blockers = append(blockers, OrganizationMemberRemovalBlocker{
			Code:    "self_remove_not_allowed",
			Message: "Cannot remove yourself from the organization.",
		})
	}
	if member.Role == organizationRoleOwner && member.Status == organizationMemberStatusActive {
		ownerCount, err := txs.countActiveOrganizationOwners(ctx, queryer, organizationID)
		if err != nil {
			return nil, err
		}
		if ownerCount <= 1 {
			blockers = append(blockers, OrganizationMemberRemovalBlocker{
				Code:    "last_org_owner",
				Message: "Cannot remove the last active organization owner.",
			})
		}
	}
	workspaceOwnerBlockers, err := txs.listSoleWorkspaceOwnerRemovalBlockers(ctx, queryer, organizationID, member.UserID)
	if err != nil {
		return nil, err
	}
	blockers = append(blockers, workspaceOwnerBlockers...)
	return blockers, nil
}

func (txs TxStore) countActiveOrganizationOwners(ctx context.Context, queryer queryer, organizationID string) (int, error) {
	var count int
	if err := queryer.QueryRow(ctx, `
SELECT COUNT(*)
FROM organization_members
WHERE organization_id = $1
  AND org_role = 'org_owner'
  AND status = 'active'
`, organizationID).Scan(&count); err != nil {
		return 0, fmt.Errorf("count active organization owners: %w", err)
	}
	return count, nil
}

func (txs TxStore) listSoleWorkspaceOwnerRemovalBlockers(ctx context.Context, queryer queryer, organizationID string, userID string) ([]OrganizationMemberRemovalBlocker, error) {
	rows, err := queryer.Query(ctx, `
SELECT w.id, w.name
FROM workspaces w
JOIN members target_member
  ON target_member.workspace_id = w.id
 AND target_member.user_id = $2
 AND target_member.type = 'human'
 AND target_member.status = 'active'
 AND target_member.workspace_role = 'owner'
WHERE w.organization_id = $1
  AND NOT EXISTS (
    SELECT 1
    FROM members other_owner
    WHERE other_owner.workspace_id = w.id
      AND other_owner.type = 'human'
      AND other_owner.status = 'active'
      AND other_owner.workspace_role = 'owner'
      AND other_owner.user_id <> $2
  )
ORDER BY w.created_at ASC, w.id ASC
`, organizationID, userID)
	if err != nil {
		return nil, fmt.Errorf("list sole workspace owner blockers: %w", err)
	}
	defer rows.Close()

	blockers := make([]OrganizationMemberRemovalBlocker, 0)
	for rows.Next() {
		var blocker OrganizationMemberRemovalBlocker
		if err := rows.Scan(&blocker.WorkspaceID, &blocker.WorkspaceName); err != nil {
			return nil, fmt.Errorf("scan sole workspace owner blocker: %w", err)
		}
		blocker.Code = "sole_workspace_owner"
		blocker.Message = "Cannot remove the sole active owner of a workspace."
		blockers = append(blockers, blocker)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate sole workspace owner blockers: %w", err)
	}
	return blockers, nil
}

func (txs TxStore) listOrganizationMemberRemovalAgents(ctx context.Context, queryer queryer, organizationID string, userID string) ([]OrganizationMemberRemovalAgent, error) {
	rows, err := queryer.Query(ctx, `
SELECT a.id, a.name, a.workspace_id
FROM agents a
JOIN machines mac
  ON mac.workspace_id = a.workspace_id
 AND mac.id = a.machine_id
JOIN workspaces w ON w.id = a.workspace_id
WHERE w.organization_id = $1
  AND mac.created_by_user_id = $2
  AND a.status <> 'archived'
ORDER BY w.created_at ASC, a.created_at ASC, a.id ASC
`, organizationID, userID)
	if err != nil {
		return nil, fmt.Errorf("list organization member removal agents: %w", err)
	}
	defer rows.Close()

	agents := make([]OrganizationMemberRemovalAgent, 0)
	for rows.Next() {
		var agent OrganizationMemberRemovalAgent
		if err := rows.Scan(&agent.ID, &agent.Name, &agent.WorkspaceID); err != nil {
			return nil, fmt.Errorf("scan organization member removal agent: %w", err)
		}
		agents = append(agents, agent)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate organization member removal agents: %w", err)
	}
	return agents, nil
}

func (txs TxStore) listOrganizationMemberRemovalMachines(ctx context.Context, queryer queryer, organizationID string, userID string) ([]OrganizationMemberRemovalMachine, error) {
	rows, err := queryer.Query(ctx, `
SELECT mac.id, mac.name, mac.workspace_id
FROM machines mac
JOIN workspaces w ON w.id = mac.workspace_id
WHERE w.organization_id = $1
  AND mac.created_by_user_id = $2
  AND mac.status = 'active'
ORDER BY w.created_at ASC, mac.created_at ASC, mac.id ASC
`, organizationID, userID)
	if err != nil {
		return nil, fmt.Errorf("list organization member removal machines: %w", err)
	}
	defer rows.Close()

	machines := make([]OrganizationMemberRemovalMachine, 0)
	for rows.Next() {
		var machine OrganizationMemberRemovalMachine
		if err := rows.Scan(&machine.ID, &machine.Name, &machine.WorkspaceID); err != nil {
			return nil, fmt.Errorf("scan organization member removal machine: %w", err)
		}
		machines = append(machines, machine)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate organization member removal machines: %w", err)
	}
	return machines, nil
}

func (txs TxStore) listOrganizationMemberRemovalWorkspaceIDs(ctx context.Context, queryer queryer, organizationID string, memberID string, userID string) ([]string, error) {
	rows, err := queryer.Query(ctx, `
SELECT workspace_id
FROM (
    SELECT w.id AS workspace_id
    FROM workspaces w
    JOIN members m
      ON m.workspace_id = w.id
     AND m.user_id = $3
     AND m.type = 'human'
     AND m.status = 'active'
    WHERE w.organization_id = $1

    UNION

    SELECT workspace_id
    FROM organization_workspace_member_grants
    WHERE organization_id = $1
      AND organization_member_id = $2
      AND status = 'active'

    UNION

    SELECT twg.workspace_id
    FROM organization_member_team_memberships tm
    JOIN organization_workspace_team_grants twg
      ON twg.organization_id = tm.organization_id
     AND twg.organization_team_id = tm.organization_team_id
     AND twg.status = 'active'
    WHERE tm.organization_id = $1
      AND tm.organization_member_id = $2
      AND tm.status = 'active'

    UNION

    SELECT workspace_id
    FROM organization_workspace_member_projections
    WHERE organization_id = $1
      AND organization_member_id = $2
) workspaces
ORDER BY workspace_id ASC
`, organizationID, memberID, userID)
	if err != nil {
		return nil, fmt.Errorf("list organization member removal workspaces: %w", err)
	}
	defer rows.Close()

	workspaceIDs := make([]string, 0)
	for rows.Next() {
		var workspaceID string
		if err := rows.Scan(&workspaceID); err != nil {
			return nil, fmt.Errorf("scan organization member removal workspace: %w", err)
		}
		workspaceIDs = append(workspaceIDs, strings.TrimSpace(workspaceID))
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate organization member removal workspaces: %w", err)
	}
	return workspaceIDs, nil
}

func (txs TxStore) deactivateOrganizationMemberAccessRows(ctx context.Context, tx coredb.Tx, organizationID string, memberID string, now time.Time) error {
	if _, err := tx.Exec(ctx, `
UPDATE organization_workspace_member_grants
SET status = 'inactive',
    updated_at = $3
WHERE organization_id = $1
  AND organization_member_id = $2
  AND status = 'active'
`, organizationID, memberID, now); err != nil {
		return fmt.Errorf("deactivate organization member workspace grants: %w", err)
	}
	if _, err := tx.Exec(ctx, `
UPDATE organization_member_team_memberships
SET status = 'inactive',
    is_primary = FALSE,
    updated_at = $3
WHERE organization_id = $1
  AND organization_member_id = $2
  AND status = 'active'
`, organizationID, memberID, now); err != nil {
		return fmt.Errorf("deactivate organization member team memberships: %w", err)
	}
	if _, err := tx.Exec(ctx, `
UPDATE organization_member_unit_memberships
SET status = 'inactive',
    is_primary = FALSE,
    updated_at = $3
WHERE organization_id = $1
  AND organization_member_id = $2
  AND status = 'active'
`, organizationID, memberID, now); err != nil {
		return fmt.Errorf("deactivate organization member unit memberships: %w", err)
	}
	return nil
}

func (txs TxStore) loadOrganizationMemberRemovalTarget(ctx context.Context, queryer queryRower, organizationID string, memberID string, forUpdate bool) (organizationMemberRemovalTarget, error) {
	statement := `
SELECT id, user_id, org_role, status, disabled_at
FROM organization_members
WHERE organization_id = $1
  AND id = $2
`
	if forUpdate {
		statement += "FOR UPDATE\n"
	}
	row := queryer.QueryRow(ctx, statement, organizationID, memberID)

	var target organizationMemberRemovalTarget
	if err := row.Scan(&target.MemberID, &target.UserID, &target.Role, &target.Status, &target.DisabledAt); err != nil {
		if coredb.IsNotFound(err) {
			return organizationMemberRemovalTarget{}, ErrNotFound
		}
		return organizationMemberRemovalTarget{}, fmt.Errorf("load organization member removal target: %w", err)
	}
	return target, nil
}
