# Copyright (c) 2025 Thomas Goirand
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
from urllib.parse import urljoin

from keystoneauth1 import adapter
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils

# Common column definitions
COMMON_COLUMNS = (
    'id',
    'vm_id',
    'vm_name',
    'scheduled_time',
    'state',
    'workflow_exec'
)

LONG_COLUMNS = (
    'id',
    'vm_id',
    'vm_name',
    'scheduled_time',
    'state',
    'workflow_exec',
    'created_at',
    'updated_at'
)


class Client(adapter.Adapter):
    """Client for the VM Migration Scheduler API."""
    
    def __init__(self, *args, **kwargs):
        kwargs.setdefault('user_agent', 'python-vmmsclient')
        kwargs.setdefault('version', '2')
        super(Client, self).__init__(*args, **kwargs)
        self.api_version = '2'

    def _get_url(self, path):
        """Build URL for API requests."""
        # Ensure path starts with /
        if not path.startswith('/'):
            path = '/' + path
        return path

    def add_vm(self, vm_identifier, scheduled_time=None):
        """Add a VM to migration scheduler."""
        url = self._get_url('vms')
        data = {
            'vm_identifier': vm_identifier
        }
        if scheduled_time:
            data['scheduled_time'] = scheduled_time
        
        try:
            resp = self.post(url, json=data)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def list_vms(self, state=None):
        """List all VM migrations."""
        url = self._get_url('vms')
        if state:
            url += f'?state={state}'
        try:
            resp = self.get(url)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def remove_vm(self, migration_id):
        """Remove a VM from migration scheduler."""
        url = self._get_url('vms/{0}'.format(migration_id))
        try:
            resp = self.delete(url)
            resp.raise_for_status()
            return True
        except Exception as e:
            raise self._handle_exception(e)

    def show_vm(self, migration_id):
        """Show details of a specific VM migration."""
        url = self._get_url('vms/{0}'.format(migration_id))
        try:
            resp = self.get(url)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def update_vm(self, migration_id, scheduled_time=None, state=None):
        """Update a VM migration."""
        url = self._get_url('vms/{0}'.format(migration_id))
        data = {}
        if scheduled_time is not None:
            data['scheduled_time'] = scheduled_time
        if state is not None:
            data['state'] = state
        try:
            resp = self.put(url, json=data)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def unset_vm(self, migration_id, scheduled_time=False):
        """Unset VM migration properties."""
        url = self._get_url('vms/{0}'.format(migration_id))
        data = {}
        if scheduled_time:
            data['scheduled_time'] = None
        try:
            resp = self.put(url, json=data)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def _is_404_error(self, e):
        """Check if exception represents a 404 Not Found error."""
        # Check if it's already a NotFound exception
        if isinstance(e, exceptions.NotFound):
            return True
        # Check if it's an HTTP error with 404 status code
        if hasattr(e, 'response') and e.response is not None:
            return getattr(e.response, 'status_code', None) == 404
        return False

    def _handle_exception(self, e):
        """Handle exceptions and raise appropriate OSC exceptions."""
        # Handle 404 errors by raising NotFound exception
        if self._is_404_error(e):
            raise exceptions.NotFound('Migration not found (404).')
        # For other errors, raise CommandError
        raise exceptions.CommandError(self._format_exception_message(e))

    def _format_exception_message(self, e):
        """Format exception message for better user experience."""
        if hasattr(e, 'response') and e.response is not None:
            try:
                error_data = e.response.json()
                if 'error' in error_data:
                    return error_data['error']
            except:
                pass
            return f"HTTP {e.response.status_code}: {e.response.reason}"
        return str(e)

    def get_vm_output(self, migration_id):
        """Get output from a VM migration.

        :param migration_id: ID of the migration
        :returns: Dictionary containing stdout, stderr, and exit_code
        """
        formatted_path = 'vms/{0}/output'.format(migration_id)
        url = self._get_url(formatted_path)
        try:
            resp = self.get(url)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)


class ShowVMCommand(command.ShowOne):
    """Show details of a VM migration."""

    def get_parser(self, prog_name):
        parser = super(ShowVMCommand, self).get_parser(prog_name)
        parser.add_argument(
            'migration_id',
            metavar='<migration-id>',
            help='Migration ID')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vmms
        data = client.show_vm(parsed_args.migration_id)

        return COMMON_COLUMNS + ('created_at', 'updated_at'), utils.get_dict_properties(data, COMMON_COLUMNS + ('created_at', 'updated_at'))


class AddVMCommand(command.ShowOne):
    """Add a VM to migration scheduler."""
    
    def get_parser(self, prog_name):
        parser = super(AddVMCommand, self).get_parser(prog_name)
        parser.add_argument(
            'vm_identifier',
            metavar='<vm-uuid-or-name>',
            help='VM UUID or name')
        parser.add_argument(
            '--schedule-time',
            metavar='<schedule-time>',
            help='Scheduled migration time (ISO format or "now")')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vmms
        
        # Handle "now" keyword for schedule time
        schedule_time = parsed_args.schedule_time
        if schedule_time and schedule_time.lower() == 'now':
            # Use current UTC time
            import datetime
            schedule_time = datetime.datetime.utcnow().isoformat() + 'Z'
        
        data = client.add_vm(
            vm_identifier=parsed_args.vm_identifier,
            scheduled_time=schedule_time)

        return COMMON_COLUMNS + ('created_at', 'updated_at'), utils.get_dict_properties(data, COMMON_COLUMNS + ('created_at', 'updated_at'))


class ListVMsCommand(command.Lister):
    """List VM migrations."""

    def get_parser(self, prog_name):
        parser = super(ListVMsCommand, self).get_parser(prog_name)
        parser.add_argument(
            '--long',
            action='store_true',
            help='Show detailed information including created_at and updated_at'
        )
        parser.add_argument(
            '--state',
            metavar='<state>',
            choices=['SCHEDULED', 'MIGRATING', 'MIGRATED', 'ERROR'],
            help='Filter by migration state'
        )
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vmms
        data = client.list_vms(state=parsed_args.state)
        
        columns = LONG_COLUMNS if parsed_args.long else COMMON_COLUMNS
        return (
            columns,
            (utils.get_dict_properties(s, columns) for s in data)
        )


class RemoveVMCommand(command.Command):
    """Remove a VM from migration scheduler."""

    def get_parser(self, prog_name):
        parser = super(RemoveVMCommand, self).get_parser(prog_name)
        parser.add_argument(
            'migration_id',
            metavar='<migration-id>',
            help='Migration ID')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vmms
        try:
            client.remove_vm(parsed_args.migration_id)
            self.app.stdout.write(
                'Migration {0} removed successfully\n'.format(
                    parsed_args.migration_id))
        except exceptions.NotFound:
            raise exceptions.CommandError(
                'Failed to remove migration {0}: not found'.format(
                    parsed_args.migration_id))


class SetVMCommand(command.ShowOne):
    """Set properties of a VM migration."""

    def get_parser(self, prog_name):
        parser = super(SetVMCommand, self).get_parser(prog_name)
        parser.add_argument(
            'migration_id',
            metavar='<migration-id>',
            help='Migration ID')
        parser.add_argument(
            '--schedule-time',
            metavar='<schedule-time>',
            help='New scheduled migration time (ISO format or "now")')
        parser.add_argument(
            '--state',
            metavar='<state>',
            choices=['SCHEDULED', 'MIGRATING', 'MIGRATED', 'ERROR'],
            help='New state (SCHEDULED, MIGRATING, MIGRATED, ERROR)')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vmms
        
        # Prepare data based on provided arguments
        if not parsed_args.schedule_time and not parsed_args.state:
            raise exceptions.CommandError('Either --schedule-time or --state must be specified')
            
        # Handle "now" keyword for schedule time
        schedule_time = parsed_args.schedule_time
        if schedule_time and schedule_time.lower() == 'now':
            # Use current UTC time
            import datetime
            schedule_time = datetime.datetime.utcnow().isoformat() + 'Z'
        
        data = client.update_vm(
            migration_id=parsed_args.migration_id,
            scheduled_time=schedule_time,
            state=parsed_args.state)

        return COMMON_COLUMNS + ('created_at', 'updated_at'), utils.get_dict_properties(data, COMMON_COLUMNS + ('created_at', 'updated_at'))


class UnsetVMCommand(command.ShowOne):
    """Unset properties of a VM migration."""

    def get_parser(self, prog_name):
        parser = super(UnsetVMCommand, self).get_parser(prog_name)
        parser.add_argument(
            'migration_id',
            metavar='<migration-id>',
            help='Migration ID')
        parser.add_argument(
            '--schedule-time',
            action='store_true',
            help='Unset scheduled migration time')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vmms
        data = client.unset_vm(
            migration_id=parsed_args.migration_id,
            scheduled_time=parsed_args.schedule_time)

        return COMMON_COLUMNS + ('created_at', 'updated_at'), utils.get_dict_properties(data, COMMON_COLUMNS + ('created_at', 'updated_at'))

class ShowVMOutputCommand(command.Command):
    """Show output from VM migration."""

    def get_parser(self, prog_name):
        parser = super(ShowVMOutputCommand, self).get_parser(prog_name)
        parser.add_argument(
            'migration_id',
            metavar='<migration-id>',
            help='Migration ID')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vmms
        try:
            data = client.get_vm_output(parsed_args.migration_id)

            # Show only stdout, equivalent to jq .stdout -r
            if 'stdout' in data:
                self.app.stdout.write(data['stdout'])
                if not data['stdout'].endswith('\n'):
                    self.app.stdout.write('\n')
            else:
                self.app.stdout.write('\n')

        except exceptions.NotFound:
            raise exceptions.CommandError(
                f'Failed to get output for migration {parsed_args.migration_id}: not found')
        except Exception as e:
            raise exceptions.CommandError(
                f'Failed to get output for migration {parsed_args.migration_id}: {str(e)}')
