# 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',
    'task_type',
    'compute_hostname',
    'scheduled_time',
    'state',
)

LONG_COLUMNS = (
    'id',
    'task_type',
    'compute_hostname',
    'scheduled_time',
    'state',
    'project_id',
    'project_name',
    'created_at',
    'updated_at'
)

SERVICE_COLUMNS = (
    'id',
    'host',
    'binary',
    'disabled',
    'last_seen_up',
)

SERVICE_LONG_COLUMNS = (
    'id',
    'host',
    'binary',
    'topic',
    'disabled',
    'disabled_reason',
    'report_count',
    'availability_zone',
    'last_seen_up',
    'created_at',
    'updated_at',
)

class Client(adapter.Adapter):
    """Client for the Vigie API."""
    
    def __init__(self, *args, **kwargs):
        kwargs.setdefault('user_agent', 'python-vigieclient')
        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 evacluate_compute(self, hostname):
        """Evacuate a compute node."""
        url = self._get_url('evacuate')
        data = {
            'hostname': hostname
        }
        
        try:
            resp = self.post(url, json=data)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def validate_compute(self, hostname):
        """Validate a compute node."""
        try:
            resp = self.post(self._get_url('validate-compute'), params={'hostname': hostname})
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def purge_project(self, project):
        """Purge a project."""
        data = {'project_id': project} if project else {}
        try:
            resp = self.post(self._get_url('purge-project'), json=data)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
             raise self._handle_exception(e)

    def services_list(self):
        """List all registered services."""
        try:
            resp = self.get(self._get_url('services-list'))
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def services_delete(self, service_ids):
        """Delete one or more services."""
        data = {'service_ids': service_ids}
        try:
            resp = self.post(self._get_url('services-delete'), json=data)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def tasks_list(self, state=None):
        """List all tasks."""
        params = {}
        if state:
            params['state'] = state
        try:
            resp = self.get(self._get_url('tasks-list'), params=params)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def tasks_delete(self, task_ids):
        """Delete one or more tasks."""
        data = {'task_ids': task_ids}
        try:
            resp = self.post(self._get_url('tasks-delete'), json=data)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def show(self, task_id):
        """Show details of a task"""
        try:
            resp = self.get(self._get_url('show/{0}'.format(task_id)))
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)

    def update(self, task_id, state=None):
        """Update a VM migration."""
        url = self._get_url('update/{0}'.format(task_id))
        data = {}
        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 _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('Resource 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 update_service(self, service_id, data):
        """Update a Vigie service."""
        url = self._get_url('service/{0}'.format(service_id))
        try:
            resp = self.put(url, json=data)
            resp.raise_for_status()
            return resp.json()
        except Exception as e:
            raise self._handle_exception(e)


class ServiceListCommand(command.Lister):
    """List Vigie services."""

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument(
            '--long',
            action='store_true',
            help='Show detailed information including disabled_reason, report_count, etc.'
        )
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vigie
        data = client.services_list()

        services = data.get('services', []) if isinstance(data, dict) else []

        columns = SERVICE_LONG_COLUMNS if parsed_args.long else SERVICE_COLUMNS
        return (
            columns,
            (utils.get_dict_properties(s, columns) for s in services)
        )


class ServiceSetCommand(command.Lister):
    """Update Vigie services."""

    def get_parser(self, prog_name):
        parser = super().get_parser(prog_name)
        parser.add_argument(
            'service_id',
            metavar='<service-id>',
            help='Service ID to update'
        )
        group = parser.add_mutually_exclusive_group(required=True)
        group.add_argument(
            '--enable',
            action='store_true',
            help='Enable the service'
        )
        group.add_argument(
            '--disable',
            action='store_true',
            help='Disable the service'
        )
        parser.add_argument(
            '--disabled-reason',
            help='Reason for disabling the service'
        )
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vigie

        # Prepare the data for the update
        data = {}

        if parsed_args.enable:
            data['enabled'] = True
        elif parsed_args.disable:
            data['enabled'] = False
            if parsed_args.disabled_reason is not None:
                data['disabled_reason'] = parsed_args.disabled_reason

        # Call the API to update the service
        try:
            result = client.update_service(parsed_args.service_id, data)

            # Ensure we have the right data structure
            if isinstance(result, dict) and 'service' in result:
                service_data = result['service']
            elif isinstance(result, dict):
                service_data = result
            else:
                service_data = {'id': parsed_args.service_id, 'disabled': not data.get('enabled', True)}

            # Format the output
            columns = SERVICE_LONG_COLUMNS

            # Return the updated service data
            return (
                columns,
                [utils.get_dict_properties(service_data, columns)]
            )
        except Exception as e:
            raise exceptions.CommandError(f"Failed to update service: {e}")


class ServiceDeleteCommand(command.ShowOne):
    """Delete one or more Vigie services."""

    def get_parser(self, prog_name):
        parser = super(ServiceDeleteCommand, self).get_parser(prog_name)
        parser.add_argument(
            'service_ids',
            metavar='<service-id>',
            nargs='+',
            help='Service ID(s) to delete'
        )
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vigie
        result = client.services_delete(parsed_args.service_ids)

        columns = ('Message', 'Deleted Count', 'Service IDs')
        data = (
            result.get('message', ''),
            result.get('deleted_count', 0),
            ', '.join(result.get('service_ids', []))
        )
        return columns, data


class ValidateComputeCommand(command.ShowOne):
    """Validate a compute node."""

    def get_parser(self, prog_name):
        parser = super(ValidateComputeCommand, self).get_parser(prog_name)
        parser.add_argument(
            'hostname',
            metavar='<hostname>',
            help='Compute hostname to validate'
        )
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vigie
        data = client.validate_compute(parsed_args.hostname)

        columns = ('message', 'task_id')
        return columns, utils.get_dict_properties(data, columns)


class EvacuateComputeCommand(command.ShowOne):
    """Evacuate a compute."""

    def get_parser(self, prog_name):
        parser = super(EvacuateComputeCommand, self).get_parser(prog_name)
        parser.add_argument(
            'hostname',
            metavar='<hostname>',
            help='Compute hostname')
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vigie
        data = client.evacluate_compute(parsed_args.hostname)

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


class TasksListCommand(command.Lister):
    """List tasks."""

    def get_parser(self, prog_name):
        parser = super(TasksListCommand, 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', 'RUNNING', 'DONE', 'ERROR'],
            help='Filter by task state'
        )
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vigie
        data = client.tasks_list(state=parsed_args.state)

        tasks = data.get('tasks', []) if isinstance(data, dict) else []        

        columns = LONG_COLUMNS if parsed_args.long else COMMON_COLUMNS
        return (
            columns,
            (utils.get_dict_properties(s, columns) for s in tasks)
        )


class TasksDeleteCommand(command.ShowOne):
    """Delete one or more tasks."""

    def get_parser(self, prog_name):
        parser = super(TasksDeleteCommand, self).get_parser(prog_name)
        parser.add_argument(
            'task_ids',
            metavar='<task-id>',
            nargs='+',
            help='Task ID(s) to delete'
        )
        return parser

    def take_action(self, parsed_args):
        client = self.app.client_manager.vigie
        result = client.tasks_delete(parsed_args.task_ids)
        
        columns = ('Message', 'Deleted Count', 'Task IDs')
        data = (result.get('message', ''), result.get('deleted_count', 0), ', '.join(result.get('task_ids', [])))
        return columns, data
