from decimal import Decimal

from django.contrib.auth.models import User
from django.db import models
from django.urls import reverse
from django.utils import timezone


class SiteSettings(models.Model):
    name = models.CharField(max_length=100, default="Teniteno1")
    tagline = models.CharField(max_length=200, blank=True)
    logo = models.ImageField(upload_to="site/", blank=True)
    contact_email = models.EmailField(default="hello@teniteno1.com")
    whatsapp = models.CharField(max_length=40, blank=True)
    location = models.CharField(max_length=100, blank=True)
    meta_description = models.TextField(blank=True)
    github_url = models.URLField(blank=True)
    linkedin_url = models.URLField(blank=True)
    x_url = models.URLField(blank=True)
    payment_provider = models.CharField(max_length=60, blank=True)
    payment_mode = models.CharField(max_length=10, default="Live")
    webhook_url = models.CharField(max_length=200, blank=True)

    class Meta:
        verbose_name_plural = "Site settings"

    def __str__(self):
        return self.name

    @classmethod
    def load(cls):
        obj = cls.objects.first()
        if obj is None:
            obj = cls.objects.create()
        return obj


class EmailSettings(models.Model):
    """How outgoing mail is sent. One row, edited from /admin/settings/?tab=email.

    The password is stored so it can be set from the panel, but it is never sent
    back to the browser — the form shows a placeholder and only writes a new
    value when you type one. If you would rather it never touched the database,
    leave it blank here and set EMAIL_HOST_PASSWORD in .env instead.
    """

    CPANEL = "cpanel"
    GOOGLE = "google"
    ZOHO = "zoho"
    BREVO = "brevo"
    CUSTOM = "custom"
    CONSOLE = "console"
    PROVIDER_CHOICES = [
        (CPANEL, "cPanel mail"),
        (GOOGLE, "Google Workspace / Gmail"),
        (ZOHO, "Zoho Mail"),
        (BREVO, "Brevo"),
        (CUSTOM, "Other SMTP server"),
        (CONSOLE, "Off — print mail to the console"),
    ]

    # Host, port and encryption each provider expects. The host for cPanel is
    # your own mail subdomain, so it is left for you to fill in.
    PRESETS = {
        CPANEL: {"host": "", "port": 465, "use_ssl": True, "use_tls": False,
                 "hint": "Usually mail.yourdomain.com. cPanel shows the exact "
                         "host under Email Accounts → Connect Devices."},
        GOOGLE: {"host": "smtp.gmail.com", "port": 587, "use_ssl": False, "use_tls": True,
                 "hint": "Use an App Password, not your account password."},
        ZOHO: {"host": "smtp.zoho.com", "port": 587, "use_ssl": False, "use_tls": True,
               "hint": "Use smtp.zoho.eu if your account is on the EU region."},
        BREVO: {"host": "smtp-relay.brevo.com", "port": 587, "use_ssl": False, "use_tls": True,
                "hint": "The username is your Brevo login; the password is an SMTP key."},
        CUSTOM: {"host": "", "port": 587, "use_ssl": False, "use_tls": True, "hint": ""},
        CONSOLE: {"host": "", "port": 0, "use_ssl": False, "use_tls": False,
                  "hint": "Nothing is sent. Mail is written to the server console."},
    }

    provider = models.CharField(max_length=20, choices=PROVIDER_CHOICES, default=CONSOLE)
    enabled = models.BooleanField(default=False, help_text="Turn all outgoing mail on or off.")

    host = models.CharField(max_length=180, blank=True)
    port = models.PositiveIntegerField(default=587)
    use_tls = models.BooleanField(default=True)
    use_ssl = models.BooleanField(default=False)
    username = models.CharField(max_length=180, blank=True)
    password = models.CharField(max_length=255, blank=True)

    from_name = models.CharField(max_length=100, default="Teniteno1")
    from_email = models.EmailField(default="hello@teniteno1.com")
    reply_to = models.EmailField(blank=True)

    on_new_lead = models.BooleanField(default=True)
    on_ticket_opened = models.BooleanField(default=True)
    on_ticket_reply = models.BooleanField(default=True)
    on_project_status = models.BooleanField(default=True)
    on_invoice_issued = models.BooleanField(default=True)
    on_payment_received = models.BooleanField(default=True)
    on_new_message = models.BooleanField(default=True)

    last_tested_at = models.DateTimeField(null=True, blank=True)
    last_test_result = models.CharField(max_length=300, blank=True)

    class Meta:
        verbose_name_plural = "Email settings"

    def __str__(self):
        return f"Email via {self.get_provider_display()}"

    @classmethod
    def load(cls):
        obj = cls.objects.first()
        if obj is None:
            obj = cls.objects.create()
        return obj

    @property
    def preset(self):
        return self.PRESETS.get(self.provider, self.PRESETS[self.CUSTOM])

    @property
    def sender(self):
        if self.from_name:
            return f"{self.from_name} <{self.from_email}>"
        return self.from_email

    def event_allowed(self, event):
        return getattr(self, f"on_{event}", True) if event else True


class Service(models.Model):
    title = models.CharField(max_length=120)
    slug = models.SlugField(unique=True)
    summary = models.CharField(max_length=300)
    included = models.TextField(help_text="One item per line")
    starting_price = models.CharField(max_length=60, blank=True)
    delivery_time = models.CharField(max_length=60, blank=True)
    order = models.PositiveIntegerField(default=0)
    published = models.BooleanField(default=True)

    class Meta:
        ordering = ["order", "title"]

    def __str__(self):
        return self.title

    def included_list(self):
        return [line.strip() for line in self.included.splitlines() if line.strip()]


class PricingPackage(models.Model):
    name = models.CharField(max_length=60)
    slug = models.SlugField(unique=True)
    audience = models.CharField(max_length=200, blank=True)
    price = models.CharField(max_length=60)
    price_note = models.CharField(max_length=60, blank=True)
    delivery_time = models.CharField(max_length=60, blank=True)
    features = models.TextField(help_text="One feature per line")
    featured = models.BooleanField(default=False)
    order = models.PositiveIntegerField(default=0)
    published = models.BooleanField(default=True)

    class Meta:
        ordering = ["order", "name"]

    def __str__(self):
        return self.name

    def feature_list(self):
        return [line.strip() for line in self.features.splitlines() if line.strip()]


class OngoingService(models.Model):
    name = models.CharField(max_length=120)
    covers = models.CharField(max_length=300)
    price = models.CharField(max_length=60)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order", "name"]

    def __str__(self):
        return self.name


class Testimonial(models.Model):
    quote = models.TextField()
    author = models.CharField(max_length=100)
    role = models.CharField(max_length=140, blank=True)
    published = models.BooleanField(default=True)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order"]

    def __str__(self):
        return self.author


class Statistic(models.Model):
    value = models.CharField(max_length=20)
    label = models.CharField(max_length=60)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order"]

    def __str__(self):
        return f"{self.value} {self.label}"


class ProcessStep(models.Model):
    title = models.CharField(max_length=60)
    description = models.CharField(max_length=300)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order"]

    def __str__(self):
        return self.title


class PortfolioCategory(models.Model):
    name = models.CharField(max_length=60)
    slug = models.SlugField(unique=True)

    class Meta:
        ordering = ["name"]
        verbose_name_plural = "Portfolio categories"

    def __str__(self):
        return self.name


class PortfolioProject(models.Model):
    name = models.CharField(max_length=140)
    slug = models.SlugField(unique=True)
    client_name = models.CharField(max_length=140, blank=True)
    category = models.ForeignKey(PortfolioCategory, on_delete=models.PROTECT, related_name="projects")
    summary = models.CharField(max_length=300)
    description = models.TextField(blank=True)
    problem = models.TextField(blank=True)
    features = models.TextField(blank=True, help_text="One feature per line")
    services = models.CharField(max_length=300, blank=True, help_text="Comma separated")
    technologies = models.CharField(max_length=300, blank=True, help_text="Comma separated")
    project_url = models.URLField(blank=True)
    github_url = models.URLField(blank=True)
    completed_on = models.DateField(null=True, blank=True)
    featured = models.BooleanField(default=False)
    published = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-completed_on", "-created_at"]

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("portfolio_detail", args=[self.slug])

    def feature_list(self):
        return [line.strip() for line in self.features.splitlines() if line.strip()]

    def tech_list(self):
        return [t.strip() for t in self.technologies.split(",") if t.strip()]

    def service_list(self):
        return [s.strip() for s in self.services.split(",") if s.strip()]


class PortfolioImage(models.Model):
    project = models.ForeignKey(PortfolioProject, on_delete=models.CASCADE, related_name="images")
    image = models.ImageField(upload_to="portfolio/")
    caption = models.CharField(max_length=200, blank=True)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order"]

    def __str__(self):
        return f"{self.project.name} image"


class Lead(models.Model):
    NEW = "new"
    CONTACTED = "contacted"
    QUOTED = "quoted"
    NEGOTIATING = "negotiating"
    WON = "won"
    LOST = "lost"
    STATUS_CHOICES = [
        (NEW, "New"),
        (CONTACTED, "Contacted"),
        (QUOTED, "Quotation sent"),
        (NEGOTIATING, "Negotiating"),
        (WON, "Won"),
        (LOST, "Lost"),
    ]

    name = models.CharField(max_length=140)
    email = models.EmailField()
    phone = models.CharField(max_length=40, blank=True)
    company = models.CharField(max_length=140, blank=True)
    project_type = models.CharField(max_length=120, blank=True)
    has_website = models.CharField(max_length=120, blank=True)
    description = models.TextField(blank=True)
    features = models.CharField(max_length=400, blank=True)
    budget = models.CharField(max_length=60, blank=True)
    timeline = models.CharField(max_length=60, blank=True)
    target_date = models.DateField(null=True, blank=True)
    references = models.TextField(blank=True)
    branding = models.CharField(max_length=120, blank=True)
    source = models.CharField(max_length=40, default="contact")
    reference = models.CharField(max_length=20, unique=True, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=NEW)
    notes = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.name} ({self.reference})"

    def save(self, *args, **kwargs):
        if not self.reference:
            last = Lead.objects.order_by("-id").first()
            self.reference = "REQ-%06d" % ((last.id + 1) if last else 1)
        super().save(*args, **kwargs)


class Client(models.Model):
    ACTIVE = "active"
    DORMANT = "dormant"
    CLOSED = "closed"
    STATUS_CHOICES = [(ACTIVE, "Active"), (DORMANT, "Dormant"), (CLOSED, "Closed")]

    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="client")
    company = models.CharField(max_length=140, blank=True)
    phone = models.CharField(max_length=40, blank=True)
    address = models.CharField(max_length=240, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=ACTIVE)
    lead = models.ForeignKey(Lead, on_delete=models.SET_NULL, null=True, blank=True, related_name="clients")
    notify_project_updates = models.BooleanField(default=True)
    notify_invoices = models.BooleanField(default=True)
    notify_payments = models.BooleanField(default=True)
    notify_messages = models.BooleanField(default=True)
    notify_tickets = models.BooleanField(default=True)
    notify_renewals = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["user__first_name", "user__last_name"]

    def __str__(self):
        return self.full_name

    @property
    def full_name(self):
        return self.user.get_full_name() or self.user.username

    @property
    def initials(self):
        parts = self.full_name.split()
        return "".join(p[0] for p in parts[:2]).upper()

    @property
    def total_paid(self):
        return sum((p.amount for p in Payment.objects.filter(project__client=self)), Decimal("0"))

    @property
    def total_balance(self):
        return sum((p.balance for p in self.projects.all()), Decimal("0"))


class Quote(models.Model):
    DRAFT = "draft"
    SENT = "sent"
    NEGOTIATING = "negotiating"
    ACCEPTED = "accepted"
    DECLINED = "declined"
    CANCELLED = "cancelled"
    STATUS_CHOICES = [
        (DRAFT, "Draft"),
        (SENT, "Sent"),
        (NEGOTIATING, "Negotiating"),
        (ACCEPTED, "Accepted"),
        (DECLINED, "Declined"),
        (CANCELLED, "Cancelled"),
    ]

    number = models.CharField(max_length=20, unique=True, blank=True)
    lead = models.ForeignKey(Lead, on_delete=models.SET_NULL, null=True, blank=True, related_name="quotes")
    client = models.ForeignKey(Client, on_delete=models.SET_NULL, null=True, blank=True, related_name="quotes")
    title = models.CharField(max_length=180)
    notes = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=DRAFT)
    valid_until = models.DateField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.number

    def save(self, *args, **kwargs):
        if not self.number:
            year = timezone.now().year
            count = Quote.objects.filter(created_at__year=year).count() + 1
            self.number = f"QT-{year}-{count:03d}"
        super().save(*args, **kwargs)

    @property
    def total(self):
        return sum((i.amount for i in self.items.all()), Decimal("0"))

    @property
    def client_name(self):
        if self.client:
            return self.client.company or self.client.full_name
        if self.lead:
            return self.lead.company or self.lead.name
        return ""


class QuoteItem(models.Model):
    quote = models.ForeignKey(Quote, on_delete=models.CASCADE, related_name="items")
    description = models.CharField(max_length=180)
    amount = models.DecimalField(max_digits=12, decimal_places=2)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order", "id"]

    def __str__(self):
        return self.description


class Project(models.Model):
    LEAD = "lead"
    QUOTATION = "quotation"
    AWAITING_PAYMENT = "awaiting_payment"
    PLANNING = "planning"
    DESIGN = "design"
    DEVELOPMENT = "development"
    TESTING = "testing"
    AWAITING_APPROVAL = "awaiting_approval"
    DEPLOYMENT = "deployment"
    COMPLETED = "completed"
    MAINTENANCE = "maintenance"
    CANCELLED = "cancelled"
    STATUS_CHOICES = [
        (LEAD, "Lead"),
        (QUOTATION, "Quotation"),
        (AWAITING_PAYMENT, "Awaiting payment"),
        (PLANNING, "Planning"),
        (DESIGN, "Design"),
        (DEVELOPMENT, "Development"),
        (TESTING, "Testing"),
        (AWAITING_APPROVAL, "Awaiting client approval"),
        (DEPLOYMENT, "Deployment"),
        (COMPLETED, "Completed"),
        (MAINTENANCE, "Maintenance"),
        (CANCELLED, "Cancelled"),
    ]
    OPEN_STATUSES = [PLANNING, DESIGN, DEVELOPMENT, TESTING, AWAITING_APPROVAL, DEPLOYMENT]

    reference = models.CharField(max_length=20, unique=True, blank=True)
    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="projects")
    quote = models.ForeignKey(Quote, on_delete=models.SET_NULL, null=True, blank=True, related_name="projects")
    name = models.CharField(max_length=180)
    slug = models.SlugField(unique=True)
    project_type = models.CharField(max_length=120, blank=True)
    description = models.TextField(blank=True)
    budget = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    status = models.CharField(max_length=30, choices=STATUS_CHOICES, default=PLANNING)
    progress = models.PositiveIntegerField(default=0)
    start_date = models.DateField(null=True, blank=True)
    deadline = models.DateField(null=True, blank=True)
    developer = models.CharField(max_length=120, blank=True)
    internal_notes = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("client_project", args=[self.slug])

    def save(self, *args, **kwargs):
        if not self.reference:
            year = timezone.now().year
            count = Project.objects.filter(created_at__year=year).count() + 1
            self.reference = f"PR-{year}-{count:03d}"
        super().save(*args, **kwargs)

    @property
    def amount_paid(self):
        return sum((p.amount for p in self.payments.all()), Decimal("0"))

    @property
    def balance(self):
        return self.budget - self.amount_paid

    @property
    def payment_status(self):
        if self.budget and self.amount_paid >= self.budget:
            return "Paid in full"
        if self.amount_paid:
            return "Partially paid"
        return "Unpaid"


class Milestone(models.Model):
    NOT_STARTED = "not_started"
    IN_PROGRESS = "in_progress"
    DONE = "done"
    STATUS_CHOICES = [
        (NOT_STARTED, "Not started"),
        (IN_PROGRESS, "In progress"),
        (DONE, "Completed"),
    ]

    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="milestones")
    title = models.CharField(max_length=140)
    detail = models.CharField(max_length=240, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=NOT_STARTED)
    due_date = models.DateField(null=True, blank=True)
    completed_on = models.DateField(null=True, blank=True)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order", "id"]

    def __str__(self):
        return f"{self.project.name} — {self.title}"


class Task(models.Model):
    TODO = "todo"
    IN_PROGRESS = "in_progress"
    REVIEW = "review"
    COMPLETED = "completed"
    STATUS_CHOICES = [
        (TODO, "To do"),
        (IN_PROGRESS, "In progress"),
        (REVIEW, "Review"),
        (COMPLETED, "Completed"),
    ]
    LOW = "low"
    NORMAL = "normal"
    HIGH = "high"
    CRITICAL = "critical"
    PRIORITY_CHOICES = [(LOW, "Low"), (NORMAL, "Normal"), (HIGH, "High"), (CRITICAL, "Critical")]

    name = models.CharField(max_length=200)
    project = models.ForeignKey(Project, on_delete=models.CASCADE, null=True, blank=True, related_name="tasks")
    priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default=NORMAL)
    due_date = models.DateField(null=True, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=TODO)
    notes = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["due_date", "-created_at"]

    def __str__(self):
        return self.name


class ProjectUpdate(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="updates")
    title = models.CharField(max_length=180)
    body = models.TextField()
    client_visible = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.title


class ProjectFile(models.Model):
    DESIGN = "design"
    DOCUMENT = "document"
    DELIVERABLE = "deliverable"
    CONTRACT = "contract"
    FOLDER_CHOICES = [
        (DESIGN, "Designs"),
        (DOCUMENT, "Documents"),
        (DELIVERABLE, "Deliverables"),
        (CONTRACT, "Contracts"),
    ]

    project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="files")
    file = models.FileField(upload_to="project-files/")
    name = models.CharField(max_length=200, blank=True)
    folder = models.CharField(max_length=20, choices=FOLDER_CHOICES, default=DOCUMENT)
    client_visible = models.BooleanField(default=True)
    uploaded_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-uploaded_at"]

    def __str__(self):
        return self.name or self.file.name

    def save(self, *args, **kwargs):
        if not self.name and self.file:
            self.name = self.file.name.rsplit("/", 1)[-1]
        super().save(*args, **kwargs)


class Website(models.Model):
    ONLINE = "online"
    OFFLINE = "offline"
    SUSPENDED = "suspended"
    STATUS_CHOICES = [(ONLINE, "Online"), (OFFLINE, "Offline"), (SUSPENDED, "Suspended")]
    MAINT_ACTIVE = "active"
    MAINT_LAPSED = "lapsed"
    MAINT_NONE = "none"
    MAINT_CHOICES = [(MAINT_ACTIVE, "Active"), (MAINT_LAPSED, "Lapsed"), (MAINT_NONE, "No plan")]

    name = models.CharField(max_length=180)
    slug = models.SlugField(unique=True)
    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="websites")
    project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name="websites")
    domain = models.CharField(max_length=180)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=ONLINE)
    built_on = models.DateField(null=True, blank=True)
    last_deployment = models.DateField(null=True, blank=True)

    registrar = models.CharField(max_length=120, blank=True)
    domain_expiry = models.DateField(null=True, blank=True)
    auto_renew = models.BooleanField(default=True)
    dns_notes = models.TextField(blank=True)

    hosting_provider = models.CharField(max_length=140, blank=True)
    hosting_plan = models.CharField(max_length=120, blank=True)
    hosting_expiry = models.DateField(null=True, blank=True)
    server_reference = models.CharField(max_length=120, blank=True)
    deployment_notes = models.TextField(blank=True)

    backend = models.CharField(max_length=180, blank=True)
    frontend = models.CharField(max_length=180, blank=True)
    database = models.CharField(max_length=120, blank=True)
    apis = models.CharField(max_length=240, blank=True)

    ssl_provider = models.CharField(max_length=120, blank=True)
    ssl_expiry = models.DateField(null=True, blank=True)

    maintenance_status = models.CharField(max_length=20, choices=MAINT_CHOICES, default=MAINT_NONE)
    last_update = models.DateField(null=True, blank=True)
    next_maintenance = models.DateField(null=True, blank=True)
    maintenance_notes = models.TextField(blank=True)

    class Meta:
        ordering = ["name"]

    def __str__(self):
        return self.name


class WebsiteCredential(models.Model):
    """Reference to a secret held in encrypted secret storage. Never store raw passwords."""

    website = models.ForeignKey(Website, on_delete=models.CASCADE, related_name="credentials")
    label = models.CharField(max_length=140)
    vault_reference = models.CharField(max_length=200, help_text="Key in the secret store, not the secret itself")
    held_by_client = models.BooleanField(default=False)
    notes = models.CharField(max_length=240, blank=True)

    class Meta:
        ordering = ["label"]

    def __str__(self):
        return f"{self.website.name} — {self.label}"


class WebsiteActivity(models.Model):
    website = models.ForeignKey(Website, on_delete=models.CASCADE, related_name="activity")
    description = models.CharField(max_length=240)
    happened_on = models.DateField(default=timezone.localdate)

    class Meta:
        ordering = ["-happened_on", "-id"]
        verbose_name_plural = "Website activity"

    def __str__(self):
        return self.description


class Invoice(models.Model):
    DRAFT = "draft"
    SENT = "sent"
    PARTIAL = "partial"
    PAID = "paid"
    OVERDUE = "overdue"
    CANCELLED = "cancelled"
    STATUS_CHOICES = [
        (DRAFT, "Draft"),
        (SENT, "Sent"),
        (PARTIAL, "Partial"),
        (PAID, "Paid"),
        (OVERDUE, "Overdue"),
        (CANCELLED, "Cancelled"),
    ]

    number = models.CharField(max_length=20, unique=True, blank=True)
    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="invoices")
    project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name="invoices")
    description = models.CharField(max_length=240, blank=True)
    total = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=DRAFT)
    issued_on = models.DateField(null=True, blank=True)
    due_on = models.DateField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.number

    def save(self, *args, **kwargs):
        if not self.number:
            year = timezone.now().year
            count = Invoice.objects.filter(created_at__year=year).count() + 1
            self.number = f"INV-{year}-{count:03d}"
        super().save(*args, **kwargs)

    @property
    def amount_paid(self):
        return sum((p.amount for p in self.payments.all()), Decimal("0"))

    @property
    def balance(self):
        return self.total - self.amount_paid

    @property
    def is_overdue(self):
        return bool(self.due_on and self.balance > 0 and self.due_on < timezone.localdate())


class InvoiceItem(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name="items")
    description = models.CharField(max_length=200)
    amount = models.DecimalField(max_digits=12, decimal_places=2)
    order = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ["order", "id"]

    def __str__(self):
        return self.description


class Payment(models.Model):
    CARD = "card"
    TRANSFER = "transfer"
    CASH = "cash"
    METHOD_CHOICES = [(CARD, "Card"), (TRANSFER, "Bank transfer"), (CASH, "Cash")]
    PENDING = "pending"
    CONFIRMED = "confirmed"
    FAILED = "failed"
    STATUS_CHOICES = [(PENDING, "Pending"), (CONFIRMED, "Confirmed"), (FAILED, "Failed")]

    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="payments")
    project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name="payments")
    invoice = models.ForeignKey(Invoice, on_delete=models.SET_NULL, null=True, blank=True, related_name="payments")
    amount = models.DecimalField(max_digits=12, decimal_places=2)
    method = models.CharField(max_length=20, choices=METHOD_CHOICES, default=TRANSFER)
    reference = models.CharField(max_length=60, unique=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=PENDING)
    paid_on = models.DateField(default=timezone.localdate)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-paid_on", "-id"]

    def __str__(self):
        return f"{self.reference} — {self.amount}"


class Ticket(models.Model):
    LOW = "low"
    NORMAL = "normal"
    HIGH = "high"
    CRITICAL = "critical"
    PRIORITY_CHOICES = [(LOW, "Low"), (NORMAL, "Normal"), (HIGH, "High"), (CRITICAL, "Critical")]
    OPEN = "open"
    PENDING = "pending"
    RESOLVED = "resolved"
    STATUS_CHOICES = [(OPEN, "Open"), (PENDING, "Pending"), (RESOLVED, "Resolved")]

    reference = models.CharField(max_length=20, unique=True, blank=True)
    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="tickets")
    project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name="tickets")
    website = models.ForeignKey(Website, on_delete=models.SET_NULL, null=True, blank=True, related_name="tickets")
    subject = models.CharField(max_length=200)
    message = models.TextField()
    attachment = models.FileField(upload_to="tickets/", blank=True)
    priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default=NORMAL)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=OPEN)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.reference} — {self.subject}"

    def save(self, *args, **kwargs):
        if not self.reference:
            last = Ticket.objects.order_by("-id").first()
            self.reference = "TK-%03d" % ((last.id + 1) if last else 1)
        super().save(*args, **kwargs)


class TicketReply(models.Model):
    ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name="replies")
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="ticket_replies")
    body = models.TextField()
    attachment = models.FileField(upload_to="tickets/", blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["created_at"]
        verbose_name_plural = "Ticket replies"

    def __str__(self):
        return f"Reply on {self.ticket.reference}"


class Message(models.Model):
    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="messages")
    project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name="messages")
    sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sent_messages")
    body = models.TextField()
    attachment = models.FileField(upload_to="messages/", blank=True)
    read = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["created_at"]

    def __str__(self):
        return f"Message from {self.sender}"

    @property
    def from_owner(self):
        return self.sender.is_staff


class Notification(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notifications")
    kind = models.CharField(max_length=40, blank=True)
    body = models.CharField(max_length=300)
    url = models.CharField(max_length=200, blank=True)
    read = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.body


class ActivityLog(models.Model):
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="activity")
    description = models.CharField(max_length=300)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.description


class LoginEvent(models.Model):
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="logins")
    username_tried = models.CharField(max_length=150, blank=True)
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    user_agent = models.CharField(max_length=300, blank=True)
    successful = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"{self.username_tried or self.user} — {'ok' if self.successful else 'failed'}"


class CalendarEvent(models.Model):
    title = models.CharField(max_length=200)
    kind = models.CharField(max_length=40, blank=True)
    date = models.DateField()
    project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, related_name="events")
    website = models.ForeignKey(Website, on_delete=models.SET_NULL, null=True, blank=True, related_name="events")

    class Meta:
        ordering = ["date"]

    def __str__(self):
        return self.title
