
import torch
import torch.nn as nn
from transformers import PreTrainedModel
from transformers.modeling_outputs import CausalLMOutputWithPast
from .configuration_syko import SykoConfig


class SykoRMSNorm(nn.Module):
    def __init__(self, hidden_size, eps=1e-5):
        super().__init__()
        self.weight = nn.Parameter(torch.ones(hidden_size))
        self.eps = eps
        
    def forward(self, x):
        # FIX 1: Karesini almadan önce veri kaybını ve taşmayı önlemek için float32'ye cast ediyoruz.
        variance = x.to(torch.float32).pow(2).mean(-1, keepdim=True)
        return x * torch.rsqrt(variance + self.eps).to(x.dtype) * self.weight


class SykoRotaryEmbedding(nn.Module):
    def __init__(self, dim, max_seq_len=2048, theta=10000.0):
        super().__init__()
        inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2).float() / dim))
        self.register_buffer("inv_freq", inv_freq)
    def forward(self, x, seq_len):
        t = torch.arange(seq_len, device=x.device).float()
        freqs = torch.outer(t, self.inv_freq)
        emb = torch.cat([freqs, freqs], dim=-1)
        return emb.cos()[None, None, :, :], emb.sin()[None, None, :, :]


def rotate_half(x):
    x1, x2 = x[..., :x.shape[-1] // 2], x[..., x.shape[-1] // 2:]
    return torch.cat([-x2, x1], dim=-1)

def apply_rotary(q, k, cos, sin):
    return (q * cos) + (rotate_half(q) * sin), (k * cos) + (rotate_half(k) * sin)


class SykoAttention(nn.Module):
    # __init__ kısmı aynı
    def __init__(self, config):
        super().__init__()
        self.hidden_size = config.hidden_size
        self.num_heads = config.num_attention_heads
        self.head_dim = config.hidden_size // config.num_attention_heads
        self.q_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
        self.k_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
        self.v_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
        self.o_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
        self.rotary = SykoRotaryEmbedding(self.head_dim, config.max_position_embeddings, config.rope_theta)

def forward(self, hidden_states, attention_mask=None, past_key_value=None, use_cache=False):
    B, T, _ = hidden_states.shape
    q = self.q_proj(hidden_states).view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
    k = self.k_proj(hidden_states).view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
    v = self.v_proj(hidden_states).view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
    cos, sin = self.rotary(q, seq_len=T)
    q, k = apply_rotary(q, k, cos, sin)
    if past_key_value is not None:
        k = torch.cat([past_key_value[0], k], dim=2)
        v = torch.cat([past_key_value[1], v], dim=2)
    present = (k, v) if use_cache else None

    # Training'de sadece is_causal=True yeterli
    # Inference'da (past_key_value varsa) causal mask zaten uygulandı
    attn_out = torch.nn.functional.scaled_dot_product_attention(
        q, k, v,
        attn_mask=None,
        is_causal=(past_key_value is None)
    )
    attn_out = attn_out.transpose(1, 2).contiguous().view(B, T, self.hidden_size)
    return self.o_proj(attn_out), present


class SykoMLP(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.gate_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
        self.up_proj   = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
        self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
    def forward(self, x):
        return self.down_proj(torch.nn.functional.silu(self.gate_proj(x)) * self.up_proj(x))


class SykoDecoderLayer(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.attn  = SykoAttention(config)
        self.mlp   = SykoMLP(config)
        self.norm1 = SykoRMSNorm(config.hidden_size, config.rms_norm_eps)
        self.norm2 = SykoRMSNorm(config.hidden_size, config.rms_norm_eps)
    def forward(self, hidden_states, attention_mask=None, past_key_value=None, use_cache=False):
        residual = hidden_states
        hidden_states, present = self.attn(self.norm1(hidden_states), attention_mask, past_key_value, use_cache)
        hidden_states = residual + hidden_states
        residual = hidden_states
        hidden_states = residual + self.mlp(self.norm2(hidden_states))
        return hidden_states, present


class SykoBaseModel(PreTrainedModel):
    config_class = SykoConfig

    def __init__(self, config):
        super().__init__(config)
        self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
        self.layers = nn.ModuleList([SykoDecoderLayer(config) for _ in range(config.num_hidden_layers)])
        self.norm = SykoRMSNorm(config.hidden_size, config.rms_norm_eps)
        self.post_init()

    def _init_weights(self, module):
        if isinstance(module, nn.Linear):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
            if module.bias is not None:
                torch.nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Embedding):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)

    def forward(self, input_ids, attention_mask=None, past_key_values=None, use_cache=False, **kwargs):
        hidden_states = self.embed_tokens(input_ids)
        presents = []
        for i, layer in enumerate(self.layers):
            pkv = past_key_values[i] if past_key_values else None
            hidden_states, present = layer(hidden_states, attention_mask, pkv, use_cache)
            if use_cache:
                presents.append(present)
        hidden_states = self.norm(hidden_states)
        return hidden_states, (presents if use_cache else None)


class SykoCausalLM(PreTrainedModel):
    config_class = SykoConfig
    
    def __init__(self, config):
        super().__init__(config)
        self.model   = SykoBaseModel(config)
        self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
        self.post_init()
        
    def _init_weights(self, module):
        if isinstance(module, nn.Linear):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
            if module.bias is not None:
                torch.nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Embedding):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)

    def forward(self, input_ids=None, attention_mask=None, past_key_values=None,
                labels=None, use_cache=None, **kwargs):
        hidden_states, presents = self.model(input_ids, attention_mask, past_key_values, use_cache or False)
        logits = self.lm_head(hidden_states)
        
        loss = None
        if labels is not None:
            shift_logits = logits[..., :-1, :].contiguous()
            shift_labels = labels[..., 1:].contiguous()
            
            # FIX 3: Loss hesaplarken fp16 taşmasını engellemek için logits değerlerini fp32'ye geçiriyoruz!
            loss_fct = nn.CrossEntropyLoss()
            loss = loss_fct(
                shift_logits.view(-1, self.config.vocab_size).to(torch.float32), 
                shift_labels.view(-1)
            )
            
        return CausalLMOutputWithPast(loss=loss, logits=logits, past_key_values=presents)
        
    def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs):
        if past_key_values:
            input_ids = input_ids[:, -1:]
        return {"input_ids": input_ids, "past_key_values": past_key_values, "use_cache": True}
