// Jenkins pipeline for YesChef.
//
// Stages:
//   1. Restore + build the .NET backend solution
//   2. Run backend unit tests
//   3. Run backend integration tests against a sidecar Postgres container named `postgres`
//      (Testcontainers also works because the docker socket is mounted)
//   4. Install, type-check, unit-test, and build the SvelteKit frontend
//   5. Build the backend and frontend Docker images from their existing Dockerfiles
//
// Requires the Jenkins Docker Pipeline plugin, an agent with Docker available, and a
// workspace user that can talk to the Docker daemon (the host docker socket is mounted
// into the build containers so Testcontainers can spawn its own DB instances).

pipeline {
    agent any

    options {
        timestamps()
        timeout(time: 45, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '20'))
        disableConcurrentBuilds()
    }

    environment {
        DOTNET_SDK_IMAGE  = 'mcr.microsoft.com/dotnet/sdk:10.0'
        NODE_IMAGE        = 'node:22-slim'
        POSTGRES_IMAGE    = 'postgres:17'

        POSTGRES_DB       = 'yeschef'
        POSTGRES_USER     = 'yeschef'
        POSTGRES_PASSWORD = 'yeschef'

        // Gitea container registry target. The registry lives on the same host as
        // Gitea itself; the owner is the Gitea user/org that owns the images.
        GITEA_REGISTRY    = 'git.therogersfamily.tech'
        GITEA_OWNER       = 'josh'
        // Jenkins credential ID for the Gitea push. Type: giteaPersonalAccessToken
        // (the gitea-personal-access-token plugin). The PAT must have at least
        // `write:package` scope. The token is bound as a Secret String at push time
        // and fed to `docker login --password-stdin` using GITEA_OWNER as the
        // username.
        GITEA_CREDENTIALS = 'gitea-ci'

        BACKEND_IMAGE     = "${GITEA_REGISTRY}/${GITEA_OWNER}/yeschef-api"
        FRONTEND_IMAGE    = "${GITEA_REGISTRY}/${GITEA_OWNER}/yeschef-web"
        IMAGE_TAG         = "${env.BUILD_NUMBER}"

        // Keep NuGet / npm caches inside the workspace so they survive across
        // docker.inside containers without needing a shared host mount.
        DOTNET_CLI_TELEMETRY_OPTOUT = '1'
        NUGET_PACKAGES              = "${env.WORKSPACE}/.nuget"
        npm_config_cache            = "${env.WORKSPACE}/.npm"
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Backend: restore & build') {
            agent {
                docker {
                    image "${DOTNET_SDK_IMAGE}"
                    reuseNode true
                }
            }
            steps {
                sh 'dotnet --info'
                sh 'dotnet restore src/backend/YesChef.slnx'
                sh 'dotnet build src/backend/YesChef.slnx -c Release --no-restore'
            }
        }

        stage('Backend: unit tests') {
            agent {
                docker {
                    image "${DOTNET_SDK_IMAGE}"
                    reuseNode true
                }
            }
            steps {
                // global.json lives in src/backend and switches dotnet test to MTP mode,
                // which requires --project (positional project paths are rejected).
                dir('src/backend') {
                    sh '''
                        dotnet test \
                            --project YesChef.Api.UnitTests/YesChef.Api.UnitTests.csproj \
                            -c Release --no-build \
                            --report-trx --results-directory ../../TestResults/backend-unit
                    '''
                }
            }
            post {
                always {
                    junit allowEmptyResults: true, testResults: 'TestResults/backend-unit/**/*.trx'
                }
            }
        }

        stage('Backend: integration tests') {
            steps {
                script {
                    // Sidecar Postgres reachable from the build container at host `postgres`.
                    // Testcontainers can still spin up additional DBs because the host docker
                    // socket is mounted into the SDK container below.
                    docker.image("${POSTGRES_IMAGE}").withRun(
                        "-e POSTGRES_DB=${POSTGRES_DB}" +
                        " -e POSTGRES_USER=${POSTGRES_USER}" +
                        " -e POSTGRES_PASSWORD=${POSTGRES_PASSWORD}"
                    ) { pg ->

                        // Wait for the DB to accept connections.
                        docker.image("${POSTGRES_IMAGE}").inside("--link ${pg.id}:postgres") {
                            sh '''
                                for i in $(seq 1 30); do
                                    if pg_isready -h postgres -U yeschef >/dev/null 2>&1; then
                                        echo "postgres is ready"
                                        exit 0
                                    fi
                                    sleep 2
                                done
                                echo "postgres did not become ready in time" >&2
                                exit 1
                            '''
                        }

                        docker.image("${DOTNET_SDK_IMAGE}").inside(
                            "--link ${pg.id}:postgres" +
                            " -v /var/run/docker.sock:/var/run/docker.sock" +
                            " -e ConnectionStrings__DefaultConnection=Host=postgres;Port=5432;Database=${POSTGRES_DB};Username=${POSTGRES_USER};Password=${POSTGRES_PASSWORD}"
                        ) {
                            dir('src/backend') {
                                sh '''
                                    dotnet test \
                                        --project YesChef.Api.IntegrationTests/YesChef.Api.IntegrationTests.csproj \
                                        -c Release \
                                        --report-trx --results-directory ../../TestResults/backend-integration
                                '''
                            }
                        }
                    }
                }
            }
            post {
                always {
                    junit allowEmptyResults: true, testResults: 'TestResults/backend-integration/**/*.trx'
                }
            }
        }

        stage('Frontend') {
            agent {
                docker {
                    image "${NODE_IMAGE}"
                    reuseNode true
                }
            }
            stages {
                stage('Install') {
                    steps {
                        dir('src/frontend') { sh 'npm ci' }
                    }
                }
                stage('Type check') {
                    steps {
                        dir('src/frontend') { sh 'npm run check' }
                    }
                }
                stage('Unit tests') {
                    steps {
                        dir('src/frontend') { sh 'npm run test:unit' }
                    }
                }
                stage('Build') {
                    steps {
                        dir('src/frontend') { sh 'npm run build' }
                    }
                }
            }
        }

        stage('Docker images') {
            steps {
                script {
                    def backendImg  = docker.build("${BACKEND_IMAGE}:${IMAGE_TAG}",  'src/backend/YesChef.Api')
                    def frontendImg = docker.build("${FRONTEND_IMAGE}:${IMAGE_TAG}", 'src/frontend')

                    backendImg.tag('latest')
                    frontendImg.tag('latest')

                    // Gitea PAT credential (giteaPersonalAccessToken) — exposes the
                    // token as a Secret String, so bind with `string` and feed it to
                    // docker login on stdin. Username for the package registry is the
                    // Gitea owner.
                    withCredentials([string(credentialsId: "${GITEA_CREDENTIALS}", variable: 'GITEA_TOKEN')]) {
                        sh """
                            set +x
                            echo "\$GITEA_TOKEN" | docker login ${GITEA_REGISTRY} -u ${GITEA_OWNER} --password-stdin
                        """
                        try {
                            sh "docker push ${BACKEND_IMAGE}:${IMAGE_TAG}"
                            sh "docker push ${BACKEND_IMAGE}:latest"
                            sh "docker push ${FRONTEND_IMAGE}:${IMAGE_TAG}"
                            sh "docker push ${FRONTEND_IMAGE}:latest"
                        } finally {
                            sh "docker logout ${GITEA_REGISTRY} || true"
                        }
                    }
                }
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'TestResults/**/*.trx', allowEmptyArchive: true, fingerprint: false
            cleanWs(deleteDirs: true, notFailBuild: true)
        }
    }
}
