test

# JIRA Module
# 更新日: 2025/12/23

# Jira APIアクセス用の認証ヘッダとプロキシ情報を生成
# 引  数: JiraのベースURL(例: https://xxx.atlassian.net)。
# 戻り値: PSCustomObject(Headers, ProxyUri)
function New-JiraAuthContext {
    param(
        [Parameter(Mandatory=$true)]
        [string]$JiraBase
    )

    $scriptRoot = $PSScriptRoot
    if (-not $scriptRoot) {$scriptRoot = (Get-Location).Path}
    $keyFile = Join-Path -Path $scriptRoot -ChildPath "JiraAPI.key"
    if (-not (Test-Path $keyFile)) {throw "ファイルが見つかりません: $keyFile"}

    $credential = Import-Csv -Path $keyFile | Select-Object -First 1
    $email    = $credential.User
    $apiToken = $credential.Key

    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    $basic = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${email}:${apiToken}"))
    $headers = @{
        "Authorization" = "Basic $basic"
        "Accept"        = "application/json"
    }

    $proxy = [System.Net.WebRequest]::GetSystemWebProxy()
    $proxyUri = $proxy.GetProxy([Uri]::new($JiraBase))

    [PSCustomObject]@{
        Headers  = $headers
        ProxyUri = $proxyUri
    }
}

# ServiceDeskのキューURLから ServiceDeskId / RequestTypeId / QueueId を特定して返します。
# 引  数: Jira Service ManagementのキューURL。
#         例: https://xxx.atlassian.net/jira/servicedesk/projects/AAA/queues/custom/833
# 戻り値: PSCustomObject(ServiceDeskId, RequestTypeId, QueueId, Name, Description)
function Get-IdsFromUrl {
    param(
        [Parameter(Mandatory=$true)]
        [string]$Url,

        [Parameter(Mandatory=$true)]
        [pscustomobject]$AuthContext
    )

    $uri = [Uri]$Url
    $jiraBaseFromUrl = "{0}://{1}" -f $uri.Scheme, $uri.Host

    if (-not $AuthContext.Headers) {
        throw "AuthContext.Headers が未設定です。New-JiraAuthContext の戻り値を指定してください。"
    }
    if (-not $AuthContext.ProxyUri) {
        throw "AuthContext.ProxyUri が未設定です。New-JiraAuthContext の戻り値を指定してください。"
    }

    $m = [regex]::Match(
        $uri.AbsolutePath,
        "(?:/jira)?/servicedesk/projects/(?<ProjectKey>[^/]+)/queues/(?<QueueType>[^/]+)/(?<QueueId>[0-9]+)(?:/)?$",
        [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
    )
    if (-not $m.Success) {
        throw "URL形式が想定と違います: $Url"
    }

    $projectKey = $m.Groups["ProjectKey"].Value
    $queueId = $m.Groups["QueueId"].Value

    $headers = $AuthContext.Headers
    $proxyToUse = $AuthContext.ProxyUri

    $serviceDeskId = $null
    $sdStart = 0
    $sdLimit = 50

    do {
        $sdUrl = "$jiraBaseFromUrl/rest/servicedeskapi/servicedesk?start=$sdStart&limit=$sdLimit"
        try {
            $sdResponse = Invoke-RestMethod -Method Get -Uri $sdUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
        } catch {
            throw "サービスデスク一覧の取得に失敗しました: $($_.Exception.Message)"
        }

        foreach ($sd in $sdResponse.values) {
            if ($sd.projectKey -eq $projectKey) {
                $serviceDeskId = [string]$sd.id
                break
            }
        }
        if ($serviceDeskId) {
            break
        }

        if ($sdResponse.PSObject.Properties.Name -contains "isLastPage") {
            if ($sdResponse.isLastPage) {
                break
            }
        } else {
            break
        }

        $sdStart += $sdLimit
    } while ($true)

    if (-not $serviceDeskId) {
        throw "ProjectKey '$projectKey' に対応するServiceDeskIdが見つかりません。"
    }

    $queueIssuesUrl = "$jiraBaseFromUrl/rest/servicedeskapi/servicedesk/$serviceDeskId/queue/$queueId/issue?start=0&limit=1"
    try {
        $queueIssues = Invoke-RestMethod -Method Get -Uri $queueIssuesUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
    } catch {
        throw "キューのリクエスト取得に失敗しました: $($_.Exception.Message)"
    }

    $requestTypeId = $null
    $requestTypeName = $null
    $requestTypeDescription = $null
    if ($queueIssues -and $queueIssues.values -and $queueIssues.values.Count -gt 0) {
        $first = $queueIssues.values[0]
        $issueKey = $null
        if ($first.PSObject.Properties.Name -contains "issueKey") {
            $issueKey = $first.issueKey
        } elseif ($first.PSObject.Properties.Name -contains "key") {
            $issueKey = $first.key
        } elseif ($first.PSObject.Properties.Name -contains "issueId") {
            $issueKey = $first.issueId
        }

        if ($issueKey) {
            $reqUrl = "$jiraBaseFromUrl/rest/servicedeskapi/request/$issueKey"
            try {
                $req = Invoke-RestMethod -Method Get -Uri $reqUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
            } catch {
                throw "リクエスト詳細の取得に失敗しました: $($_.Exception.Message)"
            }

            if ($req.PSObject.Properties.Name -contains "requestTypeId") {
                $requestTypeId = [string]$req.requestTypeId
            } elseif ($req.requestType -and ($req.requestType.PSObject.Properties.Name -contains "id")) {
                $requestTypeId = [string]$req.requestType.id
            }

            if ($req.requestType) {
                if ($req.requestType.PSObject.Properties.Name -contains "name") {
                    $requestTypeName = [string]$req.requestType.name
                }
                if ($req.requestType.PSObject.Properties.Name -contains "description") {
                    $requestTypeDescription = [string]$req.requestType.description
                }
            }
        }
    }

    if ($requestTypeId -and ((-not $requestTypeName) -or (-not $requestTypeDescription))) {
        $rtUrl = "$jiraBaseFromUrl/rest/servicedeskapi/servicedesk/$serviceDeskId/requesttype/$requestTypeId"
        try {
            $rt = Invoke-RestMethod -Method Get -Uri $rtUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
        } catch {
            $rt = $null
        }

        if ($rt) {
            if (-not $requestTypeName) {
                if ($rt.PSObject.Properties.Name -contains "name") {
                    $requestTypeName = [string]$rt.name
                }
            }
            if (-not $requestTypeDescription) {
                if ($rt.PSObject.Properties.Name -contains "description") {
                    $requestTypeDescription = [string]$rt.description
                }
            }
        }
    }

    if (-not $requestTypeId) {
        throw "RequestTypeIdを特定できません(キューにリクエストがない、または権限不足の可能性があります)。"
    }

    [PSCustomObject]@{
        ServiceDeskId = $serviceDeskId
        RequestTypeId = $requestTypeId
        QueueId       = $queueId
        Name          = $requestTypeName
        Description   = $requestTypeDescription
    }
}

function Get-QueueIssues {
    param(
        [Parameter(Mandatory=$true)]
        [string]$JiraBase,

        [Parameter(Mandatory=$true)]
        [string]$ServiceDeskId,

        [Parameter(Mandatory=$true)]
        [string]$QueueId,

        [Parameter(Mandatory=$true)]
        [pscustomobject]$AuthContext
    )

    $headers = @{} + $AuthContext.Headers
    $headers["X-Atlassian-Token"] = "no-check"
    $headers["User-Agent"] = "PowerShell JiraClient"
    $proxyToUse = $AuthContext.ProxyUri

    $url = "$JiraBase/rest/servicedeskapi/servicedesk/$ServiceDeskId/queue/$QueueId/issue"
    $allIssues = @()
    $start = 0
    $limit = 50
    $page = 0
    $activity = "Queue Issues 取得中"
    $progressId = 1

    do {
        $page++
        $currentStart = $start
        $pagedUrl = "${url}?start=${currentStart}&limit=${limit}"
        Write-Progress -Id $progressId -Activity $activity -Status ("ページ: {0} / 取得済み: {1} 件" -f $page, $allIssues.Count) -PercentComplete 0
        try {
            $response = Invoke-RestMethod -Method Get -Uri $pagedUrl -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
            $allIssues += $response.values
            $count = 0
            if ($null -ne $response.values) {
                $count = @($response.values).Count
            }
            Write-Progress -Id $progressId -Activity $activity -Status ("ページ: {0} / start={1} / 今回: {2} 件 / 累計: {3} 件 / isLastPage={4}" -f $page, $currentStart, $count, $allIssues.Count, $response.isLastPage) -CurrentOperation "" -PercentComplete 0
            $start += $limit
        } catch {
            Write-Progress -Id $progressId -Activity $activity -Completed
            Write-Host "Issues一覧の取得に失敗しました: $($_.Exception.Message)" -ForegroundColor Red
            return $null
        }
    } while (-not $response.isLastPage)

    Write-Progress -Id $progressId -Activity $activity -Completed
    return $allIssues
}

function Get-QueueJql {
    param(
        [Parameter(Mandatory=$true)]
        [string]$JiraBase,
        [Parameter(Mandatory=$true)]
        [string]$ServiceDeskId,
        [Parameter(Mandatory=$true)]
        [string]$QueueId,
        [Parameter(Mandatory=$true)]
        [pscustomobject]$AuthContext
    )

    $headers = @{} + $AuthContext.Headers
    $headers["X-Atlassian-Token"] = "no-check"
    $headers["User-Agent"] = "PowerShell JiraClient"
    $proxyToUse = $AuthContext.ProxyUri

    $url = "$JiraBase/rest/servicedeskapi/servicedesk/$ServiceDeskId/queue/$QueueId"
    try {
        $resp = Invoke-RestMethod -Method Get -Uri $url -Headers $headers -Proxy $proxyToUse -ProxyUseDefaultCredentials
        return $resp.jql
    } catch {
        Write-Host "Queue情報(JQL)の取得に失敗しました: $($_.Exception.Message)" -ForegroundColor Red
        return $null
    }
}

function Search-JiraIssuesAllPages {
    param(
        [Parameter(Mandatory=$true)]
        [string]$JiraBase,
        [Parameter(Mandatory=$true)]
        [string]$Jql,

        [string[]]$Fields,

        [Parameter(Mandatory=$true)]
        [pscustomobject]$AuthContext
    )

    $headers = @{} + $AuthContext.Headers
    $headers["X-Atlassian-Token"] = "no-check"
    $headers["User-Agent"] = "PowerShell JiraClient"
    $proxyToUse = $AuthContext.ProxyUri

    $all = @()
    $nextPageToken = $null
    $maxResults = 100

    if ([string]::IsNullOrWhiteSpace($Jql)) {
        Write-Host "エラー: JQLが空です(Queueから取得したJQLが空、または加工で消えている可能性があります)。" -ForegroundColor Red
        return $null
    }

    if ($null -eq $Fields -or $Fields.Count -eq 0) {
        $Fields = @("summary", "reporter", "created")
    }

    do {
        $url = "$JiraBase/rest/api/3/search/jql"

        $bodyParams = [ordered]@{
            jql          = $Jql
            fields       = $Fields
            maxResults   = $maxResults
            fieldsByKeys = $true
        }

        if (-not [string]::IsNullOrWhiteSpace([string]$nextPageToken)) {
            $bodyParams.Add("nextPageToken", [string]$nextPageToken)
        }

        try {
            $jsonObj = ([PSCustomObject]$bodyParams) | ConvertTo-Json -Compress -Depth 10 -ErrorAction Stop
            if ($jsonObj -is [System.Array]) {
                $json = ($jsonObj -join "")
            } else {
                $json = [string]$jsonObj
            }
        } catch {
            Write-Host "エラー: JSON変換に失敗しました: $($_.Exception.Message)" -ForegroundColor Red
            return $null
        }

        if ([string]::IsNullOrWhiteSpace($json)) {
            Write-Host "エラー: JSON変換結果が空です。" -ForegroundColor Red
            break
        }

        $enc = [System.Text.Encoding]::UTF8
        $bodyBytes = $enc.GetBytes($json)

        Write-Progress -Id 1 -Activity "Jira Search 取得中" -Status ("取得済み={0} 件" -f $all.Count) -PercentComplete 0
        try {
            $resp = Invoke-RestMethod -Method Post -Uri $url -Headers $headers -Body $bodyBytes -ContentType "application/json; charset=utf-8" -Proxy $proxyToUse -ProxyUseDefaultCredentials
        } catch {
            Write-Progress -Id 1 -Activity "Jira Search 取得中" -Completed

            $errorMsg = "Jira Search の取得に失敗しました: $($_.Exception.Message)"
            if ($_.Exception.Response) {
                try {
                    $stream = $_.Exception.Response.GetResponseStream()
                    if ($stream) {
                        $reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::UTF8)
                        $details = $reader.ReadToEnd()
                        $errorMsg += "`n詳細: $details"
                    }
                } catch {}
            }
            Write-Host $errorMsg -ForegroundColor Red
            break
        }

        if ($resp.issues) {
            $all += $resp.issues
        }

        $nextPageToken = $resp.nextPageToken

    } while ($nextPageToken)

    Write-Progress -Id 1 -Activity "Jira Search 取得中" -Completed
    return $all
}

function Set-JiraIssueField {
    param(
        [Parameter(Mandatory=$true)]
        [string]$JiraBase,
        [Parameter(Mandatory=$true)]
        [string]$IssueKey,
        [Parameter(Mandatory=$true)]
        [string]$Field,
        [Parameter(Mandatory=$true)]
        $Value,
        [Parameter(Mandatory=$true)]
        [pscustomobject]$AuthContext
    )

    if (-not $AuthContext.Headers) {
        throw "AuthContext.Headers が未設定です。New-JiraAuthContext の戻り値を指定してください。"
    }
    if (-not $AuthContext.ProxyUri) {
        throw "AuthContext.ProxyUri が未設定です。New-JiraAuthContext の戻り値を指定してください。"
    }

    $headers = @{} + $AuthContext.Headers
    $proxyToUse = $AuthContext.ProxyUri

    $url = "$JiraBase/rest/api/3/issue/$IssueKey"

    $body = @{fields = @{}}
    $body.fields[$Field] = $Value
    $json = $body | ConvertTo-Json -Depth 20 -Compress

    try {
        Invoke-RestMethod -Method Put -Uri $url -Headers $headers -Body $json -ContentType "application/json" -Proxy $proxyToUse -ProxyUseDefaultCredentials | Out-Null
        return $true
    } catch {
        $errorMsg = "Issue更新に失敗しました: $($_.Exception.Message)"
        if ($_.Exception.Response) {
            try {
                $stream = $_.Exception.Response.GetResponseStream()
                if ($stream) {
                    $reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::UTF8)
                    $details = $reader.ReadToEnd()
                    if ($details) {
                        $errorMsg += "`n詳細: $details"
                    }
                }
            } catch {}
        }
        throw $errorMsg
    }
}