from django.core.exceptions import ValidationError
from django.db import models

from shop_management.models.base import DataRoot


class LedgerAccount(DataRoot):
    class AccountType(models.TextChoices):
        ASSET = "ASSET", "Asset"
        LIABILITY = "LIABILITY", "Liability"
        EQUITY = "EQUITY", "Equity"
        INCOME = "INCOME", "Income"
        EXPENSE = "EXPENSE", "Expense"

    shop = models.ForeignKey("shop_management.Shop", on_delete=models.PROTECT, related_name="ledger_accounts")
    code = models.CharField(max_length=20)
    name = models.CharField(max_length=150)
    account_type = models.CharField(max_length=12, choices=AccountType.choices)
    parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.PROTECT, related_name="children")
    description = models.TextField(blank=True)
    is_active = models.BooleanField(default=True)
    allow_posting = models.BooleanField(default=True)
    is_system = models.BooleanField(default=False)

    class Meta:
        ordering = ["shop_id", "code"]
        constraints = [
            models.UniqueConstraint(fields=["shop", "code"], name="unique_ledger_code_per_shop"),
            models.UniqueConstraint(fields=["shop", "name"], name="unique_ledger_name_per_shop"),
        ]

    def clean(self):
        errors = {}
        if self.parent_id:
            if self.parent_id == self.pk:
                errors["parent"] = "An account cannot be its own parent."
            elif self.parent.shop_id != self.shop_id:
                errors["parent"] = "Parent account must belong to the same branch."
            elif self.parent.account_type != self.account_type:
                errors["parent"] = "Parent and child accounts must have the same account type."
        if errors:
            raise ValidationError(errors)

    def __str__(self):
        return f"{self.code} · {self.name}"


class JournalEntry(DataRoot):
    class Status(models.TextChoices):
        DRAFT = "DRAFT", "Draft"
        POSTED = "POSTED", "Posted"
        VOID = "VOID", "Cancelled"

    journal_number = models.CharField(max_length=40, unique=True, null=True, blank=True)
    shop = models.ForeignKey("shop_management.Shop", on_delete=models.PROTECT, related_name="journal_entries")
    entry_date = models.DateField()
    description = models.TextField()
    source_type = models.CharField(max_length=50, blank=True)
    source_id = models.CharField(max_length=64, blank=True)
    status = models.CharField(max_length=10, choices=Status.choices, default=Status.DRAFT)
    posted_by = models.ForeignKey("account.User", null=True, blank=True, on_delete=models.PROTECT, related_name="journal_entries_posted")
    posted_at = models.DateTimeField(null=True, blank=True)
    voided_by = models.ForeignKey("account.User", null=True, blank=True, on_delete=models.PROTECT, related_name="journal_entries_voided")
    voided_at = models.DateTimeField(null=True, blank=True)
    void_reason = models.TextField(blank=True)

    class Meta:
        ordering = ["-entry_date", "-id"]
        db_table = "shop_management_accounting_journal_entry"
        constraints = [
            models.UniqueConstraint(
                fields=["shop", "source_type", "source_id"],
                condition=~models.Q(source_type="") & ~models.Q(source_id=""),
                name="unique_journal_source_per_shop",
            )
        ]


class JournalEntryLine(DataRoot):
    journal_entry = models.ForeignKey(JournalEntry, on_delete=models.CASCADE, related_name="lines")
    ledger_account = models.ForeignKey(LedgerAccount, on_delete=models.PROTECT, related_name="journal_lines")
    customer = models.ForeignKey("shop_management.Customer", null=True, blank=True, on_delete=models.PROTECT, related_name="journal_lines")
    supplier = models.ForeignKey("shop_management.Supplier", null=True, blank=True, on_delete=models.PROTECT, related_name="journal_lines")
    financial_account = models.ForeignKey("shop_management.Account", null=True, blank=True, on_delete=models.PROTECT, related_name="journal_lines")
    transaction_currency = models.ForeignKey("shop_management.Currency", on_delete=models.PROTECT, related_name="journal_lines")
    debit = models.DecimalField(max_digits=18, decimal_places=2, default=0)
    credit = models.DecimalField(max_digits=18, decimal_places=2, default=0)
    exchange_rate_used = models.DecimalField(max_digits=20, decimal_places=6, default=1)
    base_debit = models.DecimalField(max_digits=18, decimal_places=2, default=0)
    base_credit = models.DecimalField(max_digits=18, decimal_places=2, default=0)
    description = models.CharField(max_length=255, blank=True)

    class Meta:
        ordering = ["id"]
        db_table = "shop_management_accounting_journal_line"
        constraints = [
            models.CheckConstraint(check=models.Q(debit__gte=0), name="journal_line_debit_nonnegative"),
            models.CheckConstraint(check=models.Q(credit__gte=0), name="journal_line_credit_nonnegative"),
        ]
