# Copyright (c) ModelScope Contributors. All rights reserved.
import os
import safetensors.torch
import torch
from peft import LoraConfig, PeftModel, get_peft_model
from transformers.integrations import is_deepspeed_zero3_enabled
from typing import TYPE_CHECKING, Optional

from swift.utils import deep_getattr, get_logger, get_multimodal_target_regex
from .base import Tuner

logger = get_logger()

if TYPE_CHECKING:
    from swift.arguments import SftArguments


def is_vit_aligner_param(model_arch, parameter_name: str) -> bool:
    for module_prefix in model_arch.vision_tower + model_arch.aligner:
        if f'.{module_prefix}.' in parameter_name:
            return True
    return False


class LoRALLMTuner(Tuner):
    """Full-parameter training of ViT/Aligner while LoRA training LLM"""

    @staticmethod
    def from_pretrained(model: torch.nn.Module, model_id: str, **kwargs) -> torch.nn.Module:
        model = PeftModel.from_pretrained(model, model_id, **kwargs)
        state_dict = safetensors.torch.load_file(os.path.join(model_id, 'vit.safetensors'))
        if is_deepspeed_zero3_enabled():
            import deepspeed
            params_dict = dict(model.named_parameters())
            params_to_load = {name: params_dict[name] for name in state_dict if name in params_dict}
            if params_to_load:
                with deepspeed.zero.GatheredParameters(list(params_to_load.values()), modifier_rank=0):
                    if deepspeed.comm.get_rank() == 0:
                        for name, param in params_to_load.items():
                            param.data.copy_(state_dict[name])
        else:
            model.load_state_dict(state_dict, strict=False)
        model_arch = model.model_meta.model_arch
        for module_prefix in model_arch.vision_tower + model_arch.aligner:
            deep_getattr(model, module_prefix).requires_grad_(True)
        return model

    @staticmethod
    def save_pretrained(
        model: torch.nn.Module,
        save_directory: str,
        state_dict: Optional[dict] = None,
        safe_serialization: bool = True,
        **kwargs,
    ) -> None:
        if state_dict is None:
            state_dict = {}
            for n, p in model.named_parameters():
                if p.requires_grad:
                    state_dict[n] = p.detach().cpu()
        model.save_pretrained(save_directory, state_dict=state_dict, safe_serialization=safe_serialization, **kwargs)
        # vit/aligner
        model_arch = model.model_meta.model_arch
        state_dict = {k: v for k, v in state_dict.items() if is_vit_aligner_param(model_arch, k)}
        safetensors.torch.save_file(
            state_dict, os.path.join(save_directory, 'vit.safetensors'), metadata={'format': 'pt'})

    @staticmethod
    def prepare_model(args: 'SftArguments', model: torch.nn.Module) -> torch.nn.Module:
        model_arch = model.model_meta.model_arch
        target_regex = get_multimodal_target_regex(model)
        logger.info(f'target_regex: {target_regex}')
        lora_config = LoraConfig(
            task_type=args.task_type.upper(), r=args.lora_rank, lora_alpha=args.lora_alpha, target_modules=target_regex)
        model = get_peft_model(model, lora_config)
        for module_prefix in model_arch.vision_tower + model_arch.aligner:
            deep_getattr(model, module_prefix).requires_grad_(True)
        return model
