pipeline {
    agent any

    parameters {
        choice(
            name: 'PROVISION_TYPE',
            choices: ['VM', 'LXC'],
            description: 'Select whether to provision a VM or LXC container'
        )
        choice(
            name: 'TARGET_NODE',
            choices: ['homeapp1', 'homeapp2', 'homestrg1', 'homeflux1'],
            description: 'Select the Proxmox node to provision on'
        )
        string(
            name: 'HOSTNAME',
            defaultValue: '',
            description: 'Hostname for the new VM/LXC (required)'
        )
        string(
            name: 'CPU_CORES',
            defaultValue: '2',
            description: 'Number of CPU cores'
        )
        string(
            name: 'RAM_GB',
            defaultValue: '2',
            description: 'RAM in GB'
        )
        booleanParam(
            name: 'INSTALL_ZABBIX',
            defaultValue: false,
            description: 'Install Zabbix agent2 and configure for zabbix.lan'
        )
        booleanParam(
            name: 'INSTALL_GIT',
            defaultValue: false,
            description: 'Install git'
        )
        booleanParam(
            name: 'INSTALL_DOCKER',
            defaultValue: false,
            description: 'Install Docker and Docker Compose'
        )
        booleanParam(
            name: 'INSTALL_NFS_FOLDER',
            defaultValue: false,
            description: 'Install NFS and mount NFSFolder'
        )
        booleanParam(
            name: 'INSTALL_NFS_VAULT',
            defaultValue: false,
            description: 'Install NFS and mount vault share'
        )
    }

    environment {
        ANSIBLE_HOST_KEY_CHECKING = 'False'
        ANSIBLE_FORCE_COLOR = 'true'
        OPNSENSE_HOST = '192.168.0.1'
    }

    stages {
        stage('Validate Parameters') {
            steps {
                script {
                    if (!params.HOSTNAME?.trim()) {
                        error("HOSTNAME is required")
                    }
                    if (!params.CPU_CORES.isInteger() || params.CPU_CORES.toInteger() < 1) {
                        error("CPU_CORES must be a positive integer")
                    }
                    if (!params.RAM_GB.isInteger() || params.RAM_GB.toInteger() < 1) {
                        error("RAM_GB must be a positive integer")
                    }
                }
            }
        }

        stage('Install Ansible Collections') {
            steps {
                sh 'ansible-galaxy collection install -r requirements.yml -p ./collections --force'
            }
        }

        stage('Provision VM/LXC') {
            steps {
                withCredentials([string(credentialsId: 'proxmox-resource-creator', variable: 'PROXMOX_TOKEN')]) {
                    script {
                        // Parse the token: format is user@realm!tokenid=secret
                        def tokenParts = PROXMOX_TOKEN.split('!')
                        def apiUser = tokenParts[0]
                        def tokenIdAndSecret = tokenParts[1].split('=')
                        def apiTokenId = tokenIdAndSecret[0]
                        def apiTokenSecret = tokenIdAndSecret[1]

                        sh """
                            ansible-playbook playbooks/provision.yml \
                                -e "proxmox_api_user=${apiUser}" \
                                -e "proxmox_api_token_id=${apiTokenId}" \
                                -e "proxmox_api_token_secret=${apiTokenSecret}" \
                                -e "provision_type=${params.PROVISION_TYPE}" \
                                -e "target_node=${params.TARGET_NODE}" \
                                -e "vm_hostname=${params.HOSTNAME}" \
                                -e "cpu_cores=${params.CPU_CORES}" \
                                -e "ram_gb=${params.RAM_GB}"
                        """
                    }
                }
            }
        }

        stage('Get VM IP from DHCP') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'opnsense-api', usernameVariable: 'OPNSENSE_KEY', passwordVariable: 'OPNSENSE_SECRET')]) {
                    script {
                        // LXC gets the correct hostname at clone time, VM uses template hostname
                        def lookupHost = params.PROVISION_TYPE == 'VM' ? 'ubuntu24vm' : params.HOSTNAME
                        echo "Looking up DHCP lease for hostname: ${lookupHost}"

                        // Wait for DHCP lease to appear
                        def vmIp = ''
                        timeout(time: 5, unit: 'MINUTES') {
                            waitUntil {
                                sleep(time: 10, unit: 'SECONDS')

                                // Query OPNSense DHCP leases API and parse with Python
                                vmIp = sh(
                                    script: """
                                        curl -s -k -u "\${OPNSENSE_KEY}:\${OPNSENSE_SECRET}" \
                                            "https://${OPNSENSE_HOST}/api/dhcpv4/leases/searchLease" | \
                                        python3 -c "
import sys, json
data = json.load(sys.stdin)
hostname = '${lookupHost}'
for row in data.get('rows', []):
    if row.get('hostname') == hostname or row.get('client-hostname') == hostname:
        print(row.get('address', ''))
        break
"
                                    """,
                                    returnStdout: true
                                ).trim()

                                if (vmIp) {
                                    echo "Found IP address: ${vmIp} for hostname: ${lookupHost}"
                                    return true
                                }
                                echo "DHCP lease not found yet, retrying..."
                                return false
                            }
                        }

                        // Store IP for subsequent stages
                        env.VM_IP = vmIp
                        echo "VM IP address: ${env.VM_IP}"

                        // Wait for SSH to be ready
                        timeout(time: 3, unit: 'MINUTES') {
                            waitUntil {
                                def result = sh(
                                    script: "nc -z -w5 ${env.VM_IP} 22",
                                    returnStatus: true
                                )
                                return result == 0
                            }
                        }
                        sleep(time: 10, unit: 'SECONDS')
                    }
                }
            }
        }

        stage('Set Hostname') {
            when {
                expression { params.PROVISION_TYPE == 'VM' }
            }
            steps {
                script {
                    def newHostname = params.HOSTNAME

                    // Create a temporary inventory file with the VM IP
                    writeFile file: 'temp_inventory.yml', text: """---
all:
  hosts:
    new_host:
      ansible_host: ${env.VM_IP}
      ansible_user: jenkins
      ansible_ssh_private_key_file: /var/lib/jenkins/.ssh/id_ed25519
"""

                    sh """
                        ansible-playbook playbooks/set_hostname.yml \
                            -i temp_inventory.yml \
                            -e "new_hostname=${newHostname}"
                    """
                }
            }
        }

        stage('Configure Machine') {
            steps {
                script {
                    // Create a temporary inventory file - still use IP since DNS may not have updated
                    writeFile file: 'temp_inventory.yml', text: """---
all:
  hosts:
    new_host:
      ansible_host: ${env.VM_IP}
      ansible_user: jenkins
      ansible_ssh_private_key_file: /var/lib/jenkins/.ssh/id_ed25519
"""

                    sh """
                        ansible-playbook playbooks/configure.yml \
                            -i temp_inventory.yml \
                            -e "install_zabbix=${params.INSTALL_ZABBIX}" \
                            -e "install_git=${params.INSTALL_GIT}" \
                            -e "install_docker=${params.INSTALL_DOCKER}" \
                            -e "install_nfs_folder=${params.INSTALL_NFS_FOLDER}" \
                            -e "install_nfs_vault=${params.INSTALL_NFS_VAULT}"
                    """
                }
            }
        }
    }

    post {
        always {
            // Clean up temporary inventory file
            sh 'rm -f temp_inventory.yml || true'
        }
        success {
            echo "Successfully provisioned ${params.PROVISION_TYPE} '${params.HOSTNAME}' on ${params.TARGET_NODE}"
            echo "Machine is accessible at ${params.HOSTNAME}.lan (IP: ${env.VM_IP})"
        }
        failure {
            echo "Failed to provision ${params.PROVISION_TYPE} '${params.HOSTNAME}'"
        }
    }
}
