dutybeat.psm1

# DutyBeat public API — PowerShell client.
# Works on Windows PowerShell 5.1 and PowerShell 7+ (Core). No external dependencies.

$script:DefaultBaseUri = 'https://api.dutybeat.com'
# Session state set by Connect-DutyBeat (per module load).
$script:DutyBeatContext = @{ ApiKey = $null; BaseUri = $script:DefaultBaseUri }

function Connect-DutyBeat {
    <#
    .SYNOPSIS
        Stores the API key (and optional base URL) for subsequent calls in this session.
    .DESCRIPTION
        Saves your DutyBeat API key for the current PowerShell session so you don't have to pass it to
        every command. Alternatively, set the DUTYBEAT_API_KEY environment variable and skip this call.
    .PARAMETER ApiKey
        Your API key (starts with db_live_). Create one in the app under Configuración → Claves de API.
        If omitted, the DUTYBEAT_API_KEY environment variable is used.
    .PARAMETER BaseUri
        Base URL of the API. Defaults to the production API; override only for testing.
    .EXAMPLE
        Connect-DutyBeat -ApiKey 'db_live_...'
    .LINK
        https://dutybeat.com/manuales/api
    #>

    [CmdletBinding()]
    param(
        [string]$ApiKey,
        [string]$BaseUri = $script:DefaultBaseUri
    )
    $key = if ($ApiKey) { $ApiKey } elseif ($env:DUTYBEAT_API_KEY) { $env:DUTYBEAT_API_KEY } else { $null }
    if (-not $key) {
        throw "Provide -ApiKey or set the DUTYBEAT_API_KEY environment variable."
    }
    $script:DutyBeatContext = @{ ApiKey = $key; BaseUri = $BaseUri.TrimEnd('/') }
    Write-Verbose "Connected to $($script:DutyBeatContext.BaseUri)"
}

function Invoke-DbApi {
    # Private transport: resolves the key/base URL, builds the URI, retries on 429/5xx honouring
    # Retry-After, and maps the API error envelope to a terminating error. Not exported.
    [CmdletBinding()]
    param(
        [string]$Method = 'GET',
        [Parameter(Mandatory)][string]$Path,
        [hashtable]$Query,
        [string]$ApiKey,
        [string]$BaseUri,
        [int]$MaxRetries = 2
    )
    $key = if ($ApiKey) { $ApiKey }
        elseif ($script:DutyBeatContext.ApiKey) { $script:DutyBeatContext.ApiKey }
        elseif ($env:DUTYBEAT_API_KEY) { $env:DUTYBEAT_API_KEY }
        else { $null }
    if (-not $key) {
        throw "No API key. Run Connect-DutyBeat -ApiKey '...' or set DUTYBEAT_API_KEY."
    }
    $base = if ($BaseUri) { $BaseUri.TrimEnd('/') }
        elseif ($script:DutyBeatContext.BaseUri) { $script:DutyBeatContext.BaseUri }
        else { $script:DefaultBaseUri }

    $uri = "$base$Path"
    if ($Query -and $Query.Count -gt 0) {
        $pairs = foreach ($k in $Query.Keys) { "$k=$([uri]::EscapeDataString([string]$Query[$k]))" }
        $uri += '?' + ($pairs -join '&')
    }
    $headers = @{ Authorization = "Bearer $key"; Accept = 'application/json' }

    for ($attempt = 0; ; $attempt++) {
        try {
            return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -ErrorAction Stop
        }
        catch {
            $status = $null
            try { $status = [int]$_.Exception.Response.StatusCode } catch {}

            if (($status -in 429, 500, 502, 503, 504) -and $attempt -lt $MaxRetries) {
                $delay = [Math]::Min([Math]::Pow(2, $attempt), 8)
                try {
                    $ra = $_.Exception.Response.Headers['Retry-After']
                    if ($ra) { $delay = [double]$ra }
                } catch {}
                Start-Sleep -Seconds $delay
                continue
            }

            $code = $null
            $message = $_.Exception.Message
            if ($_.ErrorDetails -and $_.ErrorDetails.Message) {
                try {
                    $body = $_.ErrorDetails.Message | ConvertFrom-Json
                    if ($body.error) { $code = $body.error.code; $message = $body.error.message }
                } catch {}
            }
            $prefix = 'DutyBeat API error'
            if ($status) { $prefix += " $status" }
            if ($code) { $prefix += " ($code)" }
            throw "${prefix}: $message"
        }
    }
}

function Get-DbUser {
    <#
    .SYNOPSIS
        Gets a user's full record (GET /api/v1/users/:id).
    .DESCRIPTION
        Returns the account and profile data for one user — the same fields shown in "Mi Perfil" in the
        app (department, work center, DNI, IBAN, SSN, and more). Requires a key with the users.get method
        enabled. A user from another company (or a non-existent id) raises a not-found error.
    .PARAMETER UserId
        The user's id within your company.
    .PARAMETER IncludeFolders
        Also return the employee's document folders (metadata only: id, name and document count).
    .PARAMETER ApiKey
        API key to use for this call. Defaults to the one from Connect-DutyBeat or DUTYBEAT_API_KEY.
    .PARAMETER BaseUri
        Base URL of the API. Defaults to the session value or the production API.
    .OUTPUTS
        PSCustomObject with the user's fields: id, full_name, email, role, status, department,
        work_center, profile (dni, iban, ssn, ...) and, if requested, folders.
    .EXAMPLE
        Get-DbUser -UserId '9f1c...' -IncludeFolders
    .EXAMPLE
        Connect-DutyBeat -ApiKey 'db_live_...'
        (Get-DbUser -UserId '9f1c...').profile.iban
    .LINK
        https://dutybeat.com/manuales/api/leer-usuario
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)][string]$UserId,
        [switch]$IncludeFolders,
        [string]$ApiKey,
        [string]$BaseUri
    )
    process {
        $query = @{}
        if ($IncludeFolders) { $query['include_folders'] = 'true' }
        $response = Invoke-DbApi -Method 'GET' -Path "/api/v1/users/$UserId" -Query $query -ApiKey $ApiKey -BaseUri $BaseUri
        $response.data
    }
}

Export-ModuleMember -Function 'Connect-DutyBeat', 'Get-DbUser'