# Workspace Locking via Gitea Issues
#
# Distributed locking mechanism using Gitea issues
#
# Version: 1.0.0

use api_client.nu *

# Lock label constants
const LOCK_LABEL_PREFIX = "workspace-lock"
const READ_LOCK_LABEL = "read-lock"
const WRITE_LOCK_LABEL = "write-lock"
const DEPLOY_LOCK_LABEL = "deploy-lock"

# Get lock repository
def get-lock-repo [] -> record {
    let config = get-gitea-config
    let org = $config.repositories.workspaces_org

    # Use special locks repository
    {org: $org, repo: "workspace-locks"}
}

# Ensure locks repository exists
def ensure-lock-repo [] {
    let lock_repo = get-lock-repo

    let result = (do {
        get-repository $lock_repo.org $lock_repo.repo
    } | complete)

    if $result.exit_code != 0 {
        # Create locks repository
        create-repository $lock_repo.org $lock_repo.repo "Workspace locking system" true false
        print $"✓ Created locks repository: ($lock_repo.org)/($lock_repo.repo)"
    }
}

# Format lock issue title
def format-lock-title [
    workspace_name: string
    lock_type: string
    user: string
] -> string {
    $"[LOCK:($lock_type)] ($workspace_name) by ($user)"
}

# Format lock issue body
def format-lock-body [
    workspace_name: string
    lock_type: string
    user: string
    operation?: string
    expiry?: string
] -> string {
    let timestamp = date now | format date "%Y-%m-%dT%H:%M:%SZ"

    let body = [
        "## Workspace Lock",
        "",
        $"- **Lock Type**: ($lock_type)",
        $"- **Workspace**: ($workspace_name)",
        $"- **User**: ($user)",
        $"- **Timestamp**: ($timestamp)",
    ]

    let body_with_operation = if ($operation | is-not-empty) {
        $body | append [$"- **Operation**: ($operation)"]
    } else {
        $body
    }

    let body_with_expiry = if ($expiry | is-not-empty) {
        $body_with_operation | append [$"- **Expiry**: ($expiry)"]
    } else {
        $body_with_operation
    }

    $body_with_expiry | str join "\n"
}

# Get lock labels
def get-lock-labels [
    lock_type: string
] -> list {
    let type_label = match $lock_type {
        "read" => $READ_LOCK_LABEL,
        "write" => $WRITE_LOCK_LABEL,
        "deploy" => $DEPLOY_LOCK_LABEL,
        _ => $lock_type
    }

    [$LOCK_LABEL_PREFIX, $type_label]
}

# Acquire workspace lock
export def acquire-workspace-lock [
    workspace_name: string
    lock_type: string  # read, write, deploy
    operation?: string
    expiry?: string
    --user: string = ""
] -> record {
    # Get current user if not specified
    let lock_user = if ($user | is-empty) {
        (get-current-user).login
    } else {
        $user
    }

    # Ensure locks repository exists
    ensure-lock-repo

    let lock_repo = get-lock-repo

    # Check for conflicting locks
    let conflicts = check-lock-conflicts $workspace_name $lock_type

    if ($conflicts | length) > 0 {
        error make {
            msg: $"Workspace ($workspace_name) is locked"
            help: $"Conflicting locks: (($conflicts | each {|c| $"#($c.number)"} | str join ", "))"
        }
    }

    # Create lock issue
    let title = format-lock-title $workspace_name $lock_type $lock_user
    let body = format-lock-body $workspace_name $lock_type $lock_user $operation $expiry
    let labels = get-lock-labels $lock_type

    let issue = create-issue $lock_repo.org $lock_repo.repo $title $body $labels

    print $"✓ Workspace lock acquired: #($issue.number)"

    {
        lock_id: $issue.number
        workspace: $workspace_name
        lock_type: $lock_type
        user: $lock_user
        timestamp: (date now | format date "%Y-%m-%dT%H:%M:%SZ")
        operation: $operation
        expiry: $expiry
    }
}

# Release workspace lock
export def release-workspace-lock [
    workspace_name: string
    lock_id: int
] -> bool {
    let lock_repo = get-lock-repo

    # Get lock issue
    let issue = get-issue $lock_repo.org $lock_repo.repo $lock_id

    # Verify it's a lock for this workspace
    if not ($issue.title | str contains $workspace_name) {
        error make {
            msg: $"Lock #($lock_id) is not for workspace ($workspace_name)"
        }
    }

    # Close issue
    close-issue $lock_repo.org $lock_repo.repo $lock_id

    print $"✓ Workspace lock released: #($lock_id)"
    true
}

# Check lock conflicts
def check-lock-conflicts [
    workspace_name: string
    lock_type: string
] -> list {
    let lock_repo = get-lock-repo

    # Get all open locks for workspace
    let all_locks = list-workspace-locks $workspace_name

    # Check for conflicts
    if $lock_type == "write" or $lock_type == "deploy" {
        # Write/deploy locks conflict with any other lock
        $all_locks
    } else if $lock_type == "read" {
        # Read locks only conflict with write/deploy locks
        $all_locks | filter {|lock|
            $lock.lock_type == "write" or $lock.lock_type == "deploy"
        }
    } else {
        []
    }
}

# Check if workspace is locked
export def is-workspace-locked [
    workspace_name: string
    lock_type: string
] -> bool {
    let conflicts = check-lock-conflicts $workspace_name $lock_type
    ($conflicts | length) > 0
}

# List active locks for workspace
export def list-workspace-locks [
    workspace_name: string
] -> list {
    let lock_repo = get-lock-repo

    # Get all open issues with lock label
    let issues = list-issues $lock_repo.org $lock_repo.repo "open" $LOCK_LABEL_PREFIX

    # Filter for this workspace
    $issues
    | where {|issue| $issue.title | str contains $workspace_name}
    | each {|issue|
        # Parse lock info from issue
        let lock_type = if ($issue.title | str contains "LOCK:write") {
            "write"
        } else if ($issue.title | str contains "LOCK:read") {
            "read"
        } else if ($issue.title | str contains "LOCK:deploy") {
            "deploy"
        } else {
            "unknown"
        }

        # Extract user from title
        let title_parts = $issue.title | split row " by "
        let user = if ($title_parts | length) > 1 {
            $title_parts.1
        } else {
            "unknown"
        }

        {
            number: $issue.number
            workspace: $workspace_name
            lock_type: $lock_type
            user: $user
            created_at: $issue.created_at
            title: $issue.title
            url: $issue.html_url
        }
    }
}

# List all active locks
export def list-all-locks [] -> list {
    let lock_repo = get-lock-repo

    # Get all open issues with lock label
    let issues = list-issues $lock_repo.org $lock_repo.repo "open" $LOCK_LABEL_PREFIX

    $issues | each {|issue|
        # Parse lock info from issue
        let lock_type = if ($issue.title | str contains "LOCK:write") {
            "write"
        } else if ($issue.title | str contains "LOCK:read") {
            "read"
        } else if ($issue.title | str contains "LOCK:deploy") {
            "deploy"
        } else {
            "unknown"
        }

        # Extract workspace and user from title
        let title_parts = $issue.title | parse "[LOCK:{type}] {workspace} by {user}"
        let parsed = if ($title_parts | length) > 0 {
            $title_parts.0
        } else {
            {workspace: "unknown", user: "unknown"}
        }

        {
            number: $issue.number
            workspace: $parsed.workspace
            lock_type: $lock_type
            user: $parsed.user
            created_at: $issue.created_at
            title: $issue.title
            url: $issue.html_url
        }
    }
}

# Force release lock (admin only)
export def force-release-lock [
    workspace_name: string
    lock_id: int
    --reason: string = "Forced unlock"
] -> bool {
    let lock_repo = get-lock-repo

    # Get lock issue
    let issue = get-issue $lock_repo.org $lock_repo.repo $lock_id

    # Add comment about forced unlock
    let current_user = (get-current-user).login
    let comment_body = $"🔓 **Forced unlock by ($current_user)**\n\nReason: ($reason)"

    # Note: Gitea API doesn't have a direct comment creation endpoint in basic API
    # For now, just close the issue
    close-issue $lock_repo.org $lock_repo.repo $lock_id

    print $"⚠️  Force released lock #($lock_id) for workspace ($workspace_name)"
    true
}

# Get lock info
export def get-lock-info [
    workspace_name: string
    lock_id: int
] -> record {
    let lock_repo = get-lock-repo

    let issue = get-issue $lock_repo.org $lock_repo.repo $lock_id

    # Verify it's a lock for this workspace
    if not ($issue.title | str contains $workspace_name) {
        error make {
            msg: $"Lock #($lock_id) is not for workspace ($workspace_name)"
        }
    }

    # Parse lock type
    let lock_type = if ($issue.title | str contains "LOCK:write") {
        "write"
    } else if ($issue.title | str contains "LOCK:read") {
        "read"
    } else if ($issue.title | str contains "LOCK:deploy") {
        "deploy"
    } else {
        "unknown"
    }

    # Extract user from title
    let title_parts = $issue.title | split row " by "
    let user = if ($title_parts | length) > 1 {
        $title_parts.1
    } else {
        "unknown"
    }

    {
        lock_id: $issue.number
        workspace: $workspace_name
        lock_type: $lock_type
        user: $user
        created_at: $issue.created_at
        updated_at: $issue.updated_at
        state: $issue.state
        title: $issue.title
        body: $issue.body
        url: $issue.html_url
        labels: $issue.labels
    }
}

# Cleanup expired locks
export def cleanup-expired-locks [] -> list {
    let lock_repo = get-lock-repo
    let now = date now

    let all_locks = list-all-locks

    # Find expired locks (based on expiry in body)
    let expired = $all_locks | each {|lock|
        let info = get-lock-info $lock.workspace $lock.number

        # Parse expiry from body
        let expiry_line = $info.body | lines | where {|line| $line | str contains "Expiry:"}
        if ($expiry_line | length) > 0 {
            let expiry_str = $expiry_line.0 | str replace "- **Expiry**: " "" | str trim
            let expiry_result = (do {
                $expiry_str | into datetime
            } | complete)

            let expiry = if $expiry_result.exit_code == 0 {
                $expiry_result.stdout
            } else {
                null
            }

            if ($expiry | is-not-empty) and ($expiry < $now) {
                $lock
            } else {
                null
            }
        } else {
            null
        }
    } | where {|x| $x != null}

    # Close expired locks
    $expired | each {|lock|
        close-issue $lock_repo.org $lock_repo.repo $lock.number
        print $"✓ Closed expired lock: #($lock.number) for ($lock.workspace)"
        $lock
    }
}

# Auto-lock wrapper for operations
export def with-workspace-lock [
    workspace_name: string
    lock_type: string
    operation: string
    command: closure
] {
    # Acquire lock
    let lock = acquire-workspace-lock $workspace_name $lock_type $operation

    # Execute command
    let cmd_result = (do {
        do $command
    } | complete)

    if $cmd_result.exit_code != 0 {
        # Release lock on error
        release-workspace-lock $workspace_name $lock.lock_id
        error make {msg: $cmd_result.stderr}
    }

    # Release lock
    release-workspace-lock $workspace_name $lock.lock_id

    $cmd_result.stdout
}
