#!/usr/bin/python -tt
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright (c) 2011 Intel, Inc.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; version 2 of the License
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., 59
# Temple Place - Suite 330, Boston, MA 02111-1307, USA.

"""Implementation of subcmd: trigger-qb-build
Trigger build on QuickBuild system.
"""

import os
import sys
import tempfile
import shutil
from xml.etree import ElementTree as et

import requests
from requests.auth import HTTPBasicAuth

from gitbuildsys.errors import GbsError
from gitbuildsys.log import LOGGER as log
from gitbuildsys.conf import configmgr


# Default template file path (installed with gbs)
DEFAULT_TEMPLATE_PATH = '/usr/share/gbs/trigger-qb-config-template.xml'

# Default variable values
DEFAULT_VARIABLES = {
    'build_type': 'Partial',
    'repo_type': 'ALL',
    'build_reference': 'Ref. Snapshot',
    'snapshot_num': '',
    'build_pkg_list_add': '',
    'build_pkg_list_modify': '',
    'build_pkg_list_rm': '',
}

# Variables that need space-to-newline conversion
MULTI_VALUE_VARS = ['build_pkg_list_add', 'build_pkg_list_modify', 'build_pkg_list_rm']


def convert_spaces_to_newlines(value):
    """Convert spaces to newlines for multi-value variables.

    This allows users to use space-separated values which will be
    converted to newline-separated format required by QuickBuild.
    """
    if not value:
        return value
    # Split by whitespace and join with newline
    items = value.split()
    return '\n'.join(items)


def read_value_from_file(filepath):
    """Read values from a file, one value per line.

    This allows users to specify a file containing package list,
    with one package per line. Empty lines and comments (#) are ignored.

    Args:
        filepath: Path to the file containing values (can be relative or absolute)

    Returns:
        String with values joined by newlines
    """
    # Convert relative path to absolute path
    abs_path = os.path.abspath(filepath)

    if not os.path.exists(abs_path):
        raise GbsError('File not found: %s (resolved to: %s)' % (filepath, abs_path))

    values = []
    with open(abs_path, 'r', encoding='utf-8') as f:
        for line in f:
            line = line.strip()
            # Skip empty lines and comments
            if line and not line.startswith('#'):
                values.append(line)

    return '\n'.join(values)


def process_variable_value(key, value):
    """Process variable value, supporting file reference.

    If value starts with '@', it's treated as a file path and
    the content will be read from that file.

    Args:
        key: Variable name
        value: Variable value (may start with '@' for file reference)

    Returns:
        Processed value
    """
    if not value:
        return value

    # Check if value is a file reference (starts with @)
    if value.startswith('@'):
        filepath = value[1:].strip()
        log.info('Reading %s from file: %s' % (key, filepath))
        return read_value_from_file(filepath)

    # Convert spaces to newlines for multi-value variables
    if key in MULTI_VALUE_VARS and (' ' in value or '\t' in value):
        return convert_spaces_to_newlines(value)

    return value


def get_qb_config():
    """Get QuickBuild configuration from config file or environment.

    Returns: dict with all config values

    Priority:
    1. Environment variables
    2. GBS config file [quickbuild] section
    """
    config = {
        'url': None,
        'conf_path': None,
        'user': None,
        'passwd': None,
        'template': None,
        'variables': {},
    }

    # Try environment variables first
    config['user'] = os.environ.get('QB_USER')
    config['passwd'] = os.environ.get('QB_PASSWORD')
    config['url'] = os.environ.get('QB_URL')
    config['conf_path'] = os.environ.get('QB_CONF_PATH')
    config['template'] = os.environ.get('QB_TEMPLATE')

    # Read variables from environment (QB_VAR_BUILD_TYPE, etc.)
    for key in DEFAULT_VARIABLES.keys():
        env_val = os.environ.get('QB_VAR_%s' % key.upper())
        if env_val:
            config['variables'][key] = env_val

    # Try GBS config file [quickbuild] section
    try:
        if configmgr.has_section('quickbuild'):
            if not config['user']:
                config['user'] = configmgr.get_optional_item('quickbuild', 'user')
            if not config['passwd']:
                config['passwd'] = configmgr.get_optional_item('quickbuild', 'passwd')
            if not config['url']:
                config['url'] = configmgr.get_optional_item('quickbuild', 'url')
            if not config['conf_path']:
                config['conf_path'] = configmgr.get_optional_item('quickbuild', 'conf_path')
            if not config['template']:
                config['template'] = configmgr.get_optional_item('quickbuild', 'template')

            # Read variables from config file
            for key in DEFAULT_VARIABLES.keys():
                if key not in config['variables']:
                    val = configmgr.get_optional_item('quickbuild', key)
                    if val:
                        config['variables'][key] = val
    except Exception as err:
        log.debug('Failed to get quickbuild config: %s' % err)

    return config


def get_template_path(config_template):
    """Get template file path.

    Priority:
    1. Command line --template
    2. Config file template
    3. Environment variable QB_TEMPLATE
    4. Default template in /usr/share/gbs/
    5. Template in gbs source directory (for development)
    """
    # Check command line and config
    if config_template and os.path.exists(config_template):
        return config_template

    # Check default installed location
    if os.path.exists(DEFAULT_TEMPLATE_PATH):
        return DEFAULT_TEMPLATE_PATH

    # Check development location (relative to this file)
    dev_template = os.path.join(os.path.dirname(__file__), '..', 'data', 'trigger-qb-config-template.xml')
    dev_template = os.path.abspath(dev_template)
    if os.path.exists(dev_template):
        return dev_template

    raise GbsError('Template file not found. Please install gbs or specify --template')


def get_configuration_id(session, url, conf_path):
    """Get configuration ID from QuickBuild."""
    full_url = "%s/rest/ids?configuration_path=%s" % (url, conf_path)
    try:
        resp = session.get(full_url, verify=False, timeout=30)
        resp.raise_for_status()
        return resp.text.strip()
    except requests.RequestException as err:
        raise GbsError('Failed to get configuration ID: %s' % err)


def render_template(template_path, config_id, variables):
    """Render XML template with variables.

    Only updates variables that are explicitly set by user.
    Variables not in the dict will keep their original values in template.
    """
    tree = et.parse(template_path)

    # Set configuration ID
    root = tree.find('configurationId')
    if root is not None:
        root.text = config_id

    # Update variables in XML - only for user-provided variables
    vars_elem = tree.find('variables')
    if vars_elem is not None:
        for entry in vars_elem.findall('entry'):
            strings = entry.findall('string')
            if len(strings) >= 2:
                key = strings[0].text
                if key and key.lower() in variables:
                    strings[1].text = variables[key.lower()]

    return tree


def trigger_build(session, url, conf_path, template_path, variables):
    """Trigger build on QuickBuild.

    Only sends user-provided variables to QuickBuild.
    Variables not set by user will keep their original values in the template,
    allowing QuickBuild to use its stored/default values.
    """
    # Get configuration ID
    config_id = get_configuration_id(session, url, conf_path)
    log.info('Configuration ID: %s' % config_id)

    # Only use user-provided variables, do NOT merge with defaults
    # This allows QuickBuild to use its stored values for unset variables
    final_vars = variables.copy()

    # Convert spaces to newlines for multi-value variables
    for key in MULTI_VALUE_VARS:
        if key in final_vars and final_vars[key]:
            # Check if value contains spaces (user used space separator)
            if ' ' in final_vars[key] or '\t' in final_vars[key]:
                final_vars[key] = convert_spaces_to_newlines(final_vars[key])
                log.debug('Converted spaces to newlines for %s' % key)

    # Render template - only updates variables that are in final_vars
    tree = render_template(template_path, config_id, final_vars)

    # Write to temp file and send request
    tmpfd, tmpfile = tempfile.mkstemp(suffix='.xml')
    try:
        tree.write(tmpfile, encoding='UTF-8', xml_declaration=True)

        req_url = "%s/rest/build_requests" % url
        with open(tmpfile, 'rb') as fobj:
            resp = session.post(req_url, data=fobj, timeout=60)

        if resp.status_code == 200 or resp.status_code == 201:
            log.info('Build triggered successfully!')
            log.info('Build info: %s' % resp.text.strip())
            return resp.text.strip()
        else:
            raise GbsError('Failed to trigger build: HTTP %d - %s' % 
                          (resp.status_code, resp.text))
    finally:
        os.close(tmpfd)
        os.remove(tmpfile)


def main(args):
    """gbs trigger-qb-build entry point."""

    # Get configuration from config file and environment
    config = get_qb_config()

    # Command line args override config file values
    url = args.url or config['url'] or 'https://quickbuild.tizen.org'
    conf_path = args.conf_path or config['conf_path']
    user = config['user']
    password = config['passwd']

    # Validate required parameters
    if not conf_path:
        raise GbsError('QuickBuild configuration path is required. '
                       'Please specify --conf-path or add conf_path to '
                       '[quickbuild] section in .gbs.conf')

    if not user or not password:
        raise GbsError('QuickBuild credentials not found. '
                       'Please set QB_USER and QB_PASSWORD environment variables, '
                       'or add [quickbuild] section with user/passwd in .gbs.conf')

    # Get template path
    template_path = get_template_path(args.template or config['template'])
    log.info('Using template: %s' % template_path)

    # Create session with auth
    session = requests.Session()
    session.auth = (user, password)

    # Disable SSL warnings for internal servers
    import urllib3
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

    log.info('QuickBuild URL: %s' % url)
    log.info('Configuration path: %s' % conf_path)

    # Merge variables: config file vars + command line vars
    variables = config['variables'].copy()
    if args.variable:
        for var in args.variable:
            if '=' in var:
                key, value = var.split('=', 1)
                variables[key.lower()] = value
            else:
                raise GbsError('Invalid variable format: %s. Use KEY=VALUE format.' % var)

    # Process variable values (handle file references and space conversion)
    for key in list(variables.keys()):
        variables[key] = process_variable_value(key, variables[key])

    # Log variables being used
    if variables:
        log.info('Variables:')
        for key, value in variables.items():
            log.info('  %s = %s' % (key, value))

    # Trigger build
    try:
        result = trigger_build(session, url, conf_path, template_path, variables)
        return 0
    except GbsError:
        raise
    except Exception as err:
        raise GbsError('Failed to trigger build: %s' % err)