# BZZZ v2 GitLab CI/CD Pipeline
# Comprehensive build, test, and deployment pipeline for BZZZ v2

variables:
  REGISTRY: registry.home.deepblack.cloud
  REGISTRY_NAMESPACE: bzzz
  GO_VERSION: "1.21"
  DOCKER_BUILDKIT: "1"
  COMPOSE_DOCKER_CLI_BUILD: "1"
  POSTGRES_VERSION: "15"
  REDIS_VERSION: "7"
  
  # Semantic versioning
  VERSION_PREFIX: "v2"
  
stages:
  - lint
  - test
  - build
  - security-scan
  - integration-test
  - deploy-staging
  - performance-test
  - deploy-production
  - post-deploy-validation

# Cache configuration
cache:
  key: "${CI_COMMIT_REF_SLUG}"
  paths:
    - .cache/go-mod/
    - .cache/docker/
    - vendor/

before_script:
  - export GOPATH=$CI_PROJECT_DIR/.cache/go-mod
  - export GOCACHE=$CI_PROJECT_DIR/.cache/go-build
  - mkdir -p .cache/{go-mod,go-build,docker}

# ================================
# LINT STAGE
# ================================

golang-lint:
  stage: lint
  image: golangci/golangci-lint:v1.55-alpine
  script:
    - golangci-lint run ./... --timeout 10m
    - go mod tidy
    - git diff --exit-code go.mod go.sum
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == "main"'
    - if: '$CI_COMMIT_BRANCH == "develop"'

dockerfile-lint:
  stage: lint
  image: hadolint/hadolint:latest-debian
  script:
    - hadolint infrastructure/dockerfiles/Dockerfile.*
    - hadolint Dockerfile
  rules:
    - changes:
        - "infrastructure/dockerfiles/*"
        - "Dockerfile*"

yaml-lint:
  stage: lint
  image: cytopia/yamllint:latest
  script:
    - yamllint infrastructure/
    - yamllint .gitlab-ci.yml
  rules:
    - changes:
        - "infrastructure/**/*.yml"
        - "infrastructure/**/*.yaml" 
        - ".gitlab-ci.yml"

# ================================
# TEST STAGE
# ================================

unit-tests:
  stage: test
  image: golang:$GO_VERSION-alpine
  services:
    - name: postgres:$POSTGRES_VERSION-alpine
      alias: postgres
    - name: redis:$REDIS_VERSION-alpine
      alias: redis
  variables:
    POSTGRES_DB: bzzz_test
    POSTGRES_USER: test
    POSTGRES_PASSWORD: testpass
    POSTGRES_HOST: postgres
    REDIS_HOST: redis
    CGO_ENABLED: 0
  before_script:
    - apk add --no-cache git make gcc musl-dev
    - export GOPATH=$CI_PROJECT_DIR/.cache/go-mod
    - export GOCACHE=$CI_PROJECT_DIR/.cache/go-build
  script:
    - go mod download
    - go test -v -race -coverprofile=coverage.out ./...
    - go tool cover -html=coverage.out -o coverage.html
    - go tool cover -func=coverage.out | grep total | awk '{print "Coverage: " $3}'
  coverage: '/Coverage: \d+\.\d+/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml
    paths:
      - coverage.html
      - coverage.out
    expire_in: 1 week

p2p-protocol-tests:
  stage: test
  image: golang:$GO_VERSION-alpine
  script:
    - apk add --no-cache git make gcc musl-dev
    - go test -v -tags=p2p ./p2p/... ./dht/... 
    - go test -v -tags=integration ./test/p2p/...
  rules:
    - changes:
        - "p2p/**/*"
        - "dht/**/*"
        - "test/p2p/**/*"

content-store-tests:
  stage: test
  image: golang:$GO_VERSION-alpine
  script:
    - apk add --no-cache git make gcc musl-dev
    - go test -v -tags=storage ./storage/... ./blake3/...
    - go test -v -benchmem -bench=. ./storage/...
  artifacts:
    paths:
      - benchmark.out
    expire_in: 1 week
  rules:
    - changes:
        - "storage/**/*"
        - "blake3/**/*"

conversation-tests:
  stage: test
  image: golang:$GO_VERSION-alpine
  services:
    - name: postgres:$POSTGRES_VERSION-alpine
      alias: postgres
  variables:
    POSTGRES_DB: bzzz_conversation_test
    POSTGRES_USER: test
    POSTGRES_PASSWORD: testpass
    POSTGRES_HOST: postgres
  script:
    - apk add --no-cache git make gcc musl-dev postgresql-client
    - until pg_isready -h postgres -p 5432 -U test; do sleep 1; done
    - go test -v -tags=conversation ./conversation/... ./threading/...
  rules:
    - changes:
        - "conversation/**/*"
        - "threading/**/*"

# ================================
# BUILD STAGE 
# ================================

build-binaries:
  stage: build
  image: golang:$GO_VERSION-alpine
  before_script:
    - apk add --no-cache git make gcc musl-dev upx
    - export GOPATH=$CI_PROJECT_DIR/.cache/go-mod
    - export GOCACHE=$CI_PROJECT_DIR/.cache/go-build
  script:
    - make build-all
    - upx --best --lzma dist/bzzz-*
    - ls -la dist/
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

build-docker-images:
  stage: build
  image: docker:24-dind
  services:
    - docker:24-dind
  variables:
    IMAGE_TAG: ${CI_COMMIT_SHORT_SHA}
    DOCKER_DRIVER: overlay2
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $REGISTRY
    - docker buildx create --use --driver docker-container
  script:
    # Build all images in parallel
    - |
      docker buildx build \
        --platform linux/amd64,linux/arm64 \
        --build-arg VERSION=${VERSION_PREFIX}.${CI_PIPELINE_ID} \
        --build-arg COMMIT=${CI_COMMIT_SHORT_SHA} \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-agent:$IMAGE_TAG \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-agent:latest \
        --file infrastructure/dockerfiles/Dockerfile.agent \
        --push .
    
    - |
      docker buildx build \
        --platform linux/amd64,linux/arm64 \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-mcp:$IMAGE_TAG \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-mcp:latest \
        --file infrastructure/dockerfiles/Dockerfile.mcp \
        --push .
    
    - |
      docker buildx build \
        --platform linux/amd64,linux/arm64 \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-openai-proxy:$IMAGE_TAG \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-openai-proxy:latest \
        --file infrastructure/dockerfiles/Dockerfile.proxy \
        --push .
    
    - |
      docker buildx build \
        --platform linux/amd64,linux/arm64 \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-resolver:$IMAGE_TAG \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-resolver:latest \
        --file infrastructure/dockerfiles/Dockerfile.resolver \
        --push .
        
    - |
      docker buildx build \
        --platform linux/amd64,linux/arm64 \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-dht:$IMAGE_TAG \
        --tag $REGISTRY/$REGISTRY_NAMESPACE/bzzz-dht:latest \
        --file infrastructure/dockerfiles/Dockerfile.dht \
        --push .
  
  dependencies:
    - build-binaries

# ================================
# SECURITY SCAN STAGE
# ================================

container-security-scan:
  stage: security-scan
  image: aquasec/trivy:latest
  script:
    - |
      for component in agent mcp openai-proxy resolver dht; do
        echo "Scanning bzzz-${component}..."
        trivy image --exit-code 1 --severity HIGH,CRITICAL \
          --format json --output trivy-${component}.json \
          $REGISTRY/$REGISTRY_NAMESPACE/bzzz-${component}:${CI_COMMIT_SHORT_SHA}
      done
  artifacts:
    reports:
      container_scanning: trivy-*.json
    expire_in: 1 week
  dependencies:
    - build-docker-images
  allow_failure: true

dependency-security-scan:
  stage: security-scan
  image: golang:$GO_VERSION-alpine
  script:
    - go install golang.org/x/vuln/cmd/govulncheck@latest
    - govulncheck ./...
  allow_failure: true

secrets-scan:
  stage: security-scan
  image: trufflesecurity/trufflehog:latest
  script:
    - trufflehog filesystem --directory=. --fail --json
  allow_failure: true
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

# ================================
# INTEGRATION TEST STAGE
# ================================

p2p-integration-test:
  stage: integration-test
  image: docker:24-dind
  services:
    - docker:24-dind
  variables:
    COMPOSE_PROJECT_NAME: bzzz-integration-${CI_PIPELINE_ID}
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $REGISTRY
    - apk add --no-cache docker-compose curl jq
  script:
    - cd infrastructure/testing
    - docker-compose -f docker-compose.integration.yml up -d
    - sleep 60  # Wait for services to start
    - ./scripts/test-p2p-mesh.sh
    - ./scripts/test-dht-discovery.sh  
    - ./scripts/test-content-addressing.sh
    - docker-compose -f docker-compose.integration.yml logs
  after_script:
    - cd infrastructure/testing
    - docker-compose -f docker-compose.integration.yml down -v
  artifacts:
    paths:
      - infrastructure/testing/test-results/
    expire_in: 1 week
    when: always
  dependencies:
    - build-docker-images

mcp-integration-test:
  stage: integration-test
  image: node:18-alpine
  services:
    - name: $REGISTRY/$REGISTRY_NAMESPACE/bzzz-mcp:${CI_COMMIT_SHORT_SHA}
      alias: mcp-server
    - name: $REGISTRY/$REGISTRY_NAMESPACE/bzzz-agent:${CI_COMMIT_SHORT_SHA} 
      alias: bzzz-agent
  script:
    - cd test/mcp
    - npm install
    - npm test
  artifacts:
    reports:
      junit: test/mcp/junit.xml
  dependencies:
    - build-docker-images

openai-proxy-test:
  stage: integration-test
  image: python:3.11-alpine
  services:
    - name: $REGISTRY/$REGISTRY_NAMESPACE/bzzz-openai-proxy:${CI_COMMIT_SHORT_SHA}
      alias: openai-proxy
    - name: redis:$REDIS_VERSION-alpine
      alias: redis
  variables:
    OPENAI_API_KEY: "test-key-mock"
    REDIS_HOST: redis
  script:
    - cd test/openai-proxy
    - pip install -r requirements.txt
    - python -m pytest -v --junitxml=junit.xml
  artifacts:
    reports:
      junit: test/openai-proxy/junit.xml
  dependencies:
    - build-docker-images

# ================================
# STAGING DEPLOYMENT
# ================================

deploy-staging:
  stage: deploy-staging
  image: docker:24-dind
  services:
    - docker:24-dind
  variables:
    DEPLOY_ENV: staging
    STACK_NAME: bzzz-v2-staging
  environment:
    name: staging
    url: https://bzzz-staging.deepblack.cloud
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan -H 192.168.1.27 >> ~/.ssh/known_hosts
  script:
    # Copy deployment files to staging environment
    - scp infrastructure/docker-compose.staging.yml tony@192.168.1.27:/rust/bzzz-v2/
    - scp infrastructure/configs/staging/* tony@192.168.1.27:/rust/bzzz-v2/config/
    
    # Deploy to staging swarm
    - |
      ssh tony@192.168.1.27 << 'EOF'
        cd /rust/bzzz-v2
        export IMAGE_TAG=${CI_COMMIT_SHORT_SHA}
        docker stack deploy -c docker-compose.staging.yml ${STACK_NAME}
        
        # Wait for deployment
        timeout 300 bash -c 'until docker service ls --filter label=com.docker.stack.namespace=${STACK_NAME} --format "{{.Replicas}}" | grep -v "0/"; do sleep 10; done'
      EOF
    
    # Health check staging deployment
    - sleep 60
    - curl -f https://bzzz-staging.deepblack.cloud/health
  dependencies:
    - build-docker-images
    - p2p-integration-test
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'
    - if: '$CI_COMMIT_BRANCH == "main"'

# ================================
# PERFORMANCE TESTING
# ================================

performance-test:
  stage: performance-test
  image: loadimpact/k6:latest
  script:
    - cd test/performance
    - k6 run --out json=performance-results.json performance-test.js
    - k6 run --out json=dht-performance.json dht-performance-test.js
  artifacts:
    paths:
      - test/performance/performance-results.json
      - test/performance/dht-performance.json
    reports:
      performance: test/performance/performance-results.json
    expire_in: 1 week
  environment:
    name: staging
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
    - when: manual
      if: '$CI_COMMIT_BRANCH == "develop"'

load-test:
  stage: performance-test
  image: python:3.11-alpine
  script:
    - cd test/load
    - pip install locust requests
    - locust --headless --users 100 --spawn-rate 10 --run-time 5m --host https://bzzz-staging.deepblack.cloud
  artifacts:
    paths:
      - test/load/locust_stats.html
    expire_in: 1 week
  environment:
    name: staging
  rules:
    - when: manual

# ================================
# PRODUCTION DEPLOYMENT
# ================================

deploy-production:
  stage: deploy-production
  image: docker:24-dind
  services:
    - docker:24-dind
  variables:
    DEPLOY_ENV: production
    STACK_NAME: bzzz-v2
  environment:
    name: production
    url: https://bzzz.deepblack.cloud
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan -H 192.168.1.27 >> ~/.ssh/known_hosts
  script:
    # Backup current production state
    - |
      ssh tony@192.168.1.27 << 'EOF'
        mkdir -p /rust/bzzz-v2/backup/$(date +%Y%m%d-%H%M%S)
        docker service ls --filter label=com.docker.stack.namespace=bzzz-v2 --format "table {{.Name}}\t{{.Image}}" > /rust/bzzz-v2/backup/$(date +%Y%m%d-%H%M%S)/pre-deployment-services.txt
      EOF
    
    # Copy production deployment files
    - scp infrastructure/docker-compose.swarm.yml tony@192.168.1.27:/rust/bzzz-v2/
    - scp infrastructure/configs/production/* tony@192.168.1.27:/rust/bzzz-v2/config/
    
    # Deploy to production with blue-green strategy
    - |
      ssh tony@192.168.1.27 << 'EOF'
        cd /rust/bzzz-v2
        export IMAGE_TAG=${CI_COMMIT_SHORT_SHA}
        
        # Deploy new version
        docker stack deploy -c docker-compose.swarm.yml ${STACK_NAME}
        
        # Wait for healthy deployment
        timeout 600 bash -c 'until docker service ls --filter label=com.docker.stack.namespace=${STACK_NAME} --format "{{.Replicas}}" | grep -v "0/" | wc -l | grep -q 8; do sleep 15; done'
        
        echo "Production deployment completed successfully"
      EOF
    
    # Verify production health
    - sleep 120
    - curl -f https://bzzz.deepblack.cloud/health
    - curl -f https://mcp.deepblack.cloud/health
  dependencies:
    - deploy-staging
    - performance-test
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
  
rollback-production:
  stage: deploy-production
  image: docker:24-dind
  variables:
    STACK_NAME: bzzz-v2
  environment:
    name: production
    action: rollback
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan -H 192.168.1.27 >> ~/.ssh/known_hosts
  script:
    - |
      ssh tony@192.168.1.27 << 'EOF'
        cd /rust/bzzz-v2
        
        # Get previous stable image tags
        PREVIOUS_TAG=$(docker service inspect bzzz-v2_bzzz-agent --format '{{.Spec.TaskTemplate.ContainerSpec.Image}}' | cut -d: -f2)
        
        # Rollback by redeploying previous version
        export IMAGE_TAG=$PREVIOUS_TAG
        docker stack deploy -c docker-compose.swarm.yml ${STACK_NAME}
        
        echo "Production rollback completed"
      EOF
  rules:
    - when: manual
      if: '$CI_COMMIT_BRANCH == "main"'

# ================================
# POST-DEPLOYMENT VALIDATION
# ================================

post-deploy-validation:
  stage: post-deploy-validation
  image: curlimages/curl:latest
  script:
    - curl -f https://bzzz.deepblack.cloud/health
    - curl -f https://mcp.deepblack.cloud/health  
    - curl -f https://resolve.deepblack.cloud/health
    - curl -f https://openai.deepblack.cloud/health
    
    # Test basic functionality
    - |
      # Test bzzz:// address resolution
      CONTENT_HASH=$(curl -s https://bzzz.deepblack.cloud/api/v2/test-content | jq -r '.hash')
      curl -f "https://resolve.deepblack.cloud/bzzz://${CONTENT_HASH}"
      
      # Test MCP endpoint
      curl -X POST https://mcp.deepblack.cloud/api/tools/list \
        -H "Content-Type: application/json" \
        -d '{"method": "tools/list"}'
  environment:
    name: production
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  needs:
    - deploy-production

smoke-tests:
  stage: post-deploy-validation
  image: golang:$GO_VERSION-alpine
  script:
    - cd test/smoke
    - go test -v ./... -base-url=https://bzzz.deepblack.cloud
  environment:
    name: production
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  needs:
    - deploy-production

# ================================
# NOTIFICATION STAGE (implicit)
# ================================

notify-success:
  stage: .post
  image: curlimages/curl:latest
  script:
    - |
      curl -X POST $SLACK_WEBHOOK_URL \
        -H 'Content-type: application/json' \
        -d '{
          "text": "🚀 BZZZ v2 Pipeline Success",
          "attachments": [{
            "color": "good",
            "fields": [{
              "title": "Branch",
              "value": "'$CI_COMMIT_BRANCH'",
              "short": true
            }, {
              "title": "Commit",
              "value": "'$CI_COMMIT_SHORT_SHA'",
              "short": true
            }, {
              "title": "Pipeline",
              "value": "'$CI_PIPELINE_URL'",
              "short": false
            }]
          }]
        }'
  rules:
    - if: '$CI_PIPELINE_STATUS == "success" && $CI_COMMIT_BRANCH == "main"'
  when: on_success

notify-failure:
  stage: .post
  image: curlimages/curl:latest
  script:
    - |
      curl -X POST $SLACK_WEBHOOK_URL \
        -H 'Content-type: application/json' \
        -d '{
          "text": "❌ BZZZ v2 Pipeline Failed",
          "attachments": [{
            "color": "danger",
            "fields": [{
              "title": "Branch", 
              "value": "'$CI_COMMIT_BRANCH'",
              "short": true
            }, {
              "title": "Commit",
              "value": "'$CI_COMMIT_SHORT_SHA'",
              "short": true
            }, {
              "title": "Pipeline",
              "value": "'$CI_PIPELINE_URL'",
              "short": false
            }]
          }]
        }'
  rules:
    - when: on_failure