import hashlib
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

TEMPLATE_SYSTEM_PROMPT = """
You are a helpful assistant designed to answer questions.
Provide concise and accurate responses, ensuring you follow the provided instructions thoroughly.
""".strip()

TARGET_CHOICES = [
    ('test', 'test'),
    ('train', 'train'),
    ('dev', 'dev'),
    ('validation', 'validation'),
]

ROLE_CHOICES = [
    ('assistant', 'assistant'),
    ('user', 'user'),
]

CLIENT_CHOICES = [
    ('openai', 'OpenAI'),
    ('ollama', 'Ollama'),
    ('genai', 'Google GenAI'),
    ('anthropic', 'Anthropic'),
]

class Dataset(models.Model):
    class Meta:
        db_table = 'dataset'
        verbose_name = 'Dataset'
        verbose_name_plural = 'Datasets'

    name = models.CharField(max_length=100)
    description = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

class QA(models.Model):
    class Meta:
        db_table = 'qa'
        verbose_name = 'QA'
        verbose_name_plural = 'QAs'

    dataset = models.ForeignKey('Dataset', on_delete=models.CASCADE)
    question = models.TextField()
    category = models.CharField(max_length=256, null=True, blank=True, db_index=True)
    extra_info = models.JSONField(null=True, blank=True)
    correct_answer = models.TextField()
    target = models.CharField(
        max_length=100, 
        default='test', 
        choices=TARGET_CHOICES,
        db_index=True
        )
    options = models.JSONField(null=True, blank=True)
    xid = models.CharField(max_length=100, null=True, blank=True)
    context = models.TextField(null=True, blank=True)
    correct_answer_idx = models.CharField(max_length=100)

    hash = models.CharField(max_length=100, unique=True)

    def __str__(self):
        return self.question[:50] + (self.question[50:] and '...')
    
    def get_hash(self):
        txt = self.dataset.name + self.question
        if self.correct_answer:
            txt += self.correct_answer
        if self.correct_answer_idx:
            txt += self.correct_answer_idx
        if self.xid:
            txt += self.xid
        if self.target:
            txt += self.target
        if self.category:
            txt += self.category
        if self.extra_info:
            txt += ''.join(self.extra_info)
        if self.context:
            txt += self.context
        if self.options:
            txt += ''.join(self.options)
        if self.context:
            txt += self.context
        return hashlib.md5(txt.encode('utf-8')).hexdigest()

    def save(self, *args, **kwargs):
        self.hash = self.get_hash()
        super().save(*args, **kwargs)

class LLMBackend(models.Model):
    class Meta:
        db_table = 'llm_backed'
        verbose_name = 'LLM Backend'
        verbose_name_plural = 'LLM Backends'
    
    name = models.CharField(max_length=100)
    client_type = models.CharField(
        max_length=100,
        default='openai',
        choices=CLIENT_CHOICES,
        db_index=True
    )
    parameters = models.JSONField(null=True, blank=True)

    def __str__(self):
        return self.name

class LLMModel(models.Model):
    class Meta:
        db_table = 'llm_model'
        verbose_name = 'LLM Model'
        verbose_name_plural = 'LLM Models'

    name = models.CharField(max_length=100)
    backend = models.ForeignKey('LLMBackend', on_delete=models.CASCADE)
    parameters = models.JSONField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"[{self.id}] {self.name}"

class EvalConfig(models.Model):
    class Meta:
        db_table = 'eval_config'
        verbose_name = 'EvalConfig'
        verbose_name_plural = 'EvalConfig'

    name = models.CharField(max_length=100)
    description = models.TextField(null=True, blank=True)
    dataset = models.ForeignKey('Dataset', on_delete=models.CASCADE)
    sys_prompt = models.TextField(default=TEMPLATE_SYSTEM_PROMPT)
    final_answer_pattern = models.TextField(default='(?i)Final Answer\s*:\s*([A-E])')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name
    
class RoleMessage(models.Model):
    class Meta:
        db_table = 'role_message'
        verbose_name = 'RoleMessage'
        verbose_name_plural = 'RoleMessage'

    role = models.CharField(max_length=100, choices=ROLE_CHOICES, db_index=True)
    content = models.TextField()
    eval_config = models.ForeignKey('EvalConfig', on_delete=models.CASCADE)

    def __str__(self):
        return self.content[:50] + (self.content[50:] and '...')
    
class EvalSession(models.Model):
    class Meta:
        db_table = 'eval_session'
        verbose_name = 'EvalSession'
        verbose_name_plural = 'EvalSession'
    name = models.CharField(max_length=100)
    config = models.ForeignKey('EvalConfig', on_delete=models.CASCADE)
    llm_model = models.ForeignKey('LLMModel', on_delete=models.CASCADE)
    answer_interpreter = models.ForeignKey('AnswerInterpreter', on_delete=models.SET_NULL, null=True, blank=True)
    parameters = models.JSONField(null=True, blank=True)
    dataset_target = models.CharField(
        max_length=100, 
        default='test', 
        choices=TARGET_CHOICES,
        db_index=True
    )
    request_delay = models.IntegerField(
        null=True, blank=True, 
        validators=[MinValueValidator(1), MaxValueValidator(600)],
        help_text="Request delay in seconds"
        )
    is_active = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    def __str__(self):
        return f"{self.name} [{self.config.name} - {self.llm_model.name}]"

    def save(self, *args, **kwargs):
        if self.pk is None:
            self.parameters = self.llm_model.parameters
        super().save(*args, **kwargs)
    
    # accuracy property
    @property
    def accuracy(self):
        total_counts = self.evalanswer_set.count()
        if total_counts == 0:
            return 0
        return self.evalanswer_set.filter(is_correct=True).count() / self.evalanswer_set.count()
                     
class EvalAnswer(models.Model):
    class Meta:
        db_table = 'eval_answer'
        verbose_name = 'EvalAnswer'
        verbose_name_plural = 'EvalAnswer'

    eval_session = models.ForeignKey('EvalSession', on_delete=models.CASCADE)
    question = models.ForeignKey('QA', on_delete=models.CASCADE)
    instruction = models.TextField()
    assistant_answer = models.TextField()
    is_correct = models.BooleanField()
    created_at = models.DateTimeField(auto_now_add=True)
    hash = models.CharField(max_length=100)
    llm_backend = models.ForeignKey('LLMBackend', on_delete=models.CASCADE)
    llm_model = models.ForeignKey('LLMModel', on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.question} - {self.is_correct}"
    

class AnswerInterpreter(models.Model):
    class Meta:
        db_table = 'answer_interpreter'
        verbose_name = 'AnswerInterpreter'
        verbose_name_plural = 'AnswerInterpreter'

    name = models.CharField(max_length=100)
    prompt = models.TextField()
    llm_model = models.ForeignKey('LLMModel', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name