from __future__ import annotations

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils import timezone
from orgs.models import Organization


User = get_user_model()


class TimestampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


## Organization and related tenancy models moved to orgs.models


class MaterialCategory(TimestampedModel):
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="material_categories")
    name = models.CharField(max_length=255)

    class Meta:
        unique_together = ("organization", "name")

    def __str__(self):
        return self.name


class Material(TimestampedModel):
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="materials")
    category = models.ForeignKey(MaterialCategory, on_delete=models.PROTECT, related_name="materials")
    name = models.CharField(max_length=255)
    code = models.CharField(max_length=64, blank=True)
    # unit choices keep MVP simple; conversions out of scope for now
    UNIT_KG = "kg"
    UNIT_LB = "lb"
    UNIT_PCS = "pcs"
    UNIT_CHOICES = (
        (UNIT_KG, "Kilogram"),
        (UNIT_LB, "Pound"),
        (UNIT_PCS, "Pieces"),
    )
    default_unit = models.CharField(max_length=8, choices=UNIT_CHOICES, default=UNIT_KG)

    class Meta:
        unique_together = ("organization", "name")

    def __str__(self):
        return self.name


class PriceList(TimestampedModel):
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="price_lists")
    name = models.CharField(max_length=255)
    currency_code = models.CharField(max_length=8, default="USD")
    effective_from = models.DateField(null=True, blank=True)
    effective_to = models.DateField(null=True, blank=True)

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


class PriceListItem(TimestampedModel):
    DIRECTION_BUY = "buy"   # we pay the customer
    DIRECTION_SELL = "sell" # we invoice the customer
    DIRECTION_CHOICES = (
        (DIRECTION_BUY, "Buy from customer"),
        (DIRECTION_SELL, "Sell to customer"),
    )

    price_list = models.ForeignKey(PriceList, on_delete=models.CASCADE, related_name="items")
    material = models.ForeignKey(Material, on_delete=models.PROTECT, related_name="price_items")
    unit = models.CharField(max_length=8, choices=Material.UNIT_CHOICES, default=Material.UNIT_KG)
    unit_price = models.DecimalField(max_digits=12, decimal_places=2)
    direction = models.CharField(max_length=8, choices=DIRECTION_CHOICES, default=DIRECTION_SELL)

    class Meta:
        unique_together = ("price_list", "material", "unit", "direction")


class Customer(TimestampedModel):
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="customers")
    name = models.CharField(max_length=255)
    email = models.EmailField(blank=True)
    phone = models.CharField(max_length=64, blank=True)
    billing_address = models.TextField(blank=True)
    price_list = models.ForeignKey(PriceList, on_delete=models.SET_NULL, null=True, blank=True, related_name="customers")

    def __str__(self):
        return self.name


class CustomerSite(TimestampedModel):
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="sites")
    name = models.CharField(max_length=255, blank=True)
    address = models.TextField()
    contact_name = models.CharField(max_length=255, blank=True)
    contact_phone = models.CharField(max_length=64, blank=True)
    contact_email = models.EmailField(blank=True)

    def __str__(self):
        return self.name or f"Site {self.id}"


class ServiceAgreement(TimestampedModel):
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="agreements")
    site = models.ForeignKey(CustomerSite, on_delete=models.SET_NULL, null=True, blank=True, related_name="agreements")
    price_list = models.ForeignKey(PriceList, on_delete=models.PROTECT, related_name="agreements")
    valid_from = models.DateField(null=True, blank=True)
    valid_to = models.DateField(null=True, blank=True)


class PickupOrder(TimestampedModel):
    STATUS_REQUESTED = "requested"
    STATUS_SCHEDULED = "scheduled"
    STATUS_EN_ROUTE = "en_route"
    STATUS_COLLECTING = "collecting"
    STATUS_AT_FACILITY = "at_facility"
    STATUS_WEIGHED = "weighed"
    STATUS_INVOICED = "invoiced"
    STATUS_COMPLETED = "completed"
    STATUS_CANCELED = "canceled"
    STATUS_CHOICES = (
        (STATUS_REQUESTED, "Requested"),
        (STATUS_SCHEDULED, "Scheduled"),
        (STATUS_EN_ROUTE, "En Route"),
        (STATUS_COLLECTING, "Collecting"),
        (STATUS_AT_FACILITY, "At Facility"),
        (STATUS_WEIGHED, "Weighed"),
        (STATUS_INVOICED, "Invoiced"),
        (STATUS_COMPLETED, "Completed"),
        (STATUS_CANCELED, "Canceled"),
    )

    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="pickup_orders")
    customer = models.ForeignKey(Customer, on_delete=models.PROTECT, related_name="pickup_orders")
    site = models.ForeignKey(CustomerSite, on_delete=models.PROTECT, related_name="pickup_orders")
    status = models.CharField(max_length=16, choices=STATUS_CHOICES, default=STATUS_REQUESTED, db_index=True)
    scheduled_at = models.DateTimeField(null=True, blank=True)
    assigned_driver = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="assigned_pickups")
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="created_pickups")
    completed_at = models.DateTimeField(null=True, blank=True)
    notes = models.TextField(blank=True)

    def __str__(self):
        return f"Pickup #{self.id} - {self.customer.name}"


class PickupItem(TimestampedModel):
    pickup = models.ForeignKey(PickupOrder, on_delete=models.CASCADE, related_name="items")
    material = models.ForeignKey(Material, on_delete=models.PROTECT)
    estimated_qty = models.DecimalField(max_digits=12, decimal_places=3, null=True, blank=True)
    unit = models.CharField(max_length=8, choices=Material.UNIT_CHOICES, default=Material.UNIT_KG)


class WeighTicket(TimestampedModel):
    pickup = models.OneToOneField(PickupOrder, on_delete=models.CASCADE, related_name="weigh_ticket")
    ticket_number = models.CharField(max_length=64, blank=True)
    gross_weight = models.DecimalField(max_digits=12, decimal_places=3)
    tare_weight = models.DecimalField(max_digits=12, decimal_places=3)
    net_weight = models.DecimalField(max_digits=12, decimal_places=3)
    unit = models.CharField(max_length=8, choices=Material.UNIT_CHOICES, default=Material.UNIT_KG)
    recorded_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="weigh_tickets")
    recorded_at = models.DateTimeField(default=timezone.now)


class WeighLine(TimestampedModel):
    ticket = models.ForeignKey(WeighTicket, on_delete=models.CASCADE, related_name="lines")
    material = models.ForeignKey(Material, on_delete=models.PROTECT)
    quantity = models.DecimalField(max_digits=12, decimal_places=3)
    unit = models.CharField(max_length=8, choices=Material.UNIT_CHOICES, default=Material.UNIT_KG)


class Invoice(TimestampedModel):
    STATUS_DRAFT = "draft"
    STATUS_ISSUED = "issued"
    STATUS_PAID = "paid"
    STATUS_VOID = "void"
    STATUS_CHOICES = (
        (STATUS_DRAFT, "Draft"),
        (STATUS_ISSUED, "Issued"),
        (STATUS_PAID, "Paid"),
        (STATUS_VOID, "Void"),
    )

    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="invoices")
    customer = models.ForeignKey(Customer, on_delete=models.PROTECT, related_name="invoices")
    pickup = models.ForeignKey(PickupOrder, on_delete=models.SET_NULL, null=True, blank=True, related_name="invoices")
    currency_code = models.CharField(max_length=8, default="USD")
    total_amount = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    status = models.CharField(max_length=8, choices=STATUS_CHOICES, default=STATUS_DRAFT, db_index=True)
    issued_at = models.DateTimeField(null=True, blank=True)
    due_at = models.DateTimeField(null=True, blank=True)


class InvoiceLine(TimestampedModel):
    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name="lines")
    description = models.CharField(max_length=255)
    material = models.ForeignKey(Material, on_delete=models.SET_NULL, null=True, blank=True)
    quantity = models.DecimalField(max_digits=12, decimal_places=3)
    unit = models.CharField(max_length=8, choices=Material.UNIT_CHOICES, default=Material.UNIT_KG)
    unit_price = models.DecimalField(max_digits=12, decimal_places=2)
    line_total = models.DecimalField(max_digits=14, decimal_places=2)


class Payment(TimestampedModel):
    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name="payments")
    amount = models.DecimalField(max_digits=14, decimal_places=2)
    currency_code = models.CharField(max_length=8, default="USD")
    received_at = models.DateTimeField(default=timezone.now)
    reference = models.CharField(max_length=128, blank=True)


class Payout(TimestampedModel):
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="payouts")
    customer = models.ForeignKey(Customer, on_delete=models.PROTECT, related_name="payouts")
    pickup = models.ForeignKey(PickupOrder, on_delete=models.SET_NULL, null=True, blank=True, related_name="payouts")
    amount = models.DecimalField(max_digits=14, decimal_places=2)
    currency_code = models.CharField(max_length=8, default="USD")
    paid_at = models.DateTimeField(default=timezone.now)
    reference = models.CharField(max_length=128, blank=True)


class Document(TimestampedModel):
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="documents")
    file = models.FileField(upload_to="documents/%Y/%m/%d/")
    kind = models.CharField(max_length=64, blank=True)
    # Generic attachment to any model instance
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey("content_type", "object_id")
    uploaded_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)


class AuditLog(TimestampedModel):
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="audit_logs")
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    action = models.CharField(max_length=64)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey("content_type", "object_id")
    metadata = models.JSONField(default=dict, blank=True)

    class Meta:
        indexes = [
            models.Index(fields=["organization", "created_at"]),
            models.Index(fields=["action", "created_at"]),
        ]

class ScrapListing(TimestampedModel):
    TYPE_OPEN = "open"
    TYPE_SEALED = "sealed"
    AUCTION_CHOICES = ((TYPE_OPEN, "Open"), (TYPE_SEALED, "Sealed"))

    STATUS_DRAFT = "draft"
    STATUS_OPEN = "open"
    STATUS_CLOSED = "closed"
    STATUS_AWARDED = "awarded"
    STATUS_CANCELED = "canceled"
    STATUS_CHOICES = (
        (STATUS_DRAFT, "Draft"),
        (STATUS_OPEN, "Open"),
        (STATUS_CLOSED, "Closed"),
        (STATUS_AWARDED, "Awarded"),
        (STATUS_CANCELED, "Canceled"),
    )

    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="scrap_listings")
    customer = models.ForeignKey(Customer, on_delete=models.PROTECT, related_name="scrap_listings")
    site = models.ForeignKey(CustomerSite, on_delete=models.PROTECT, related_name="scrap_listings")
    title = models.CharField(max_length=255)
    description = models.TextField(blank=True)
    auction_type = models.CharField(max_length=16, choices=AUCTION_CHOICES, default=TYPE_OPEN)
    currency_code = models.CharField(max_length=8, default="USD")
    reserve_price = models.DecimalField(max_digits=14, decimal_places=2, null=True, blank=True)
    min_increment = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    starts_at = models.DateTimeField(null=True, blank=True)
    ends_at = models.DateTimeField(null=True, blank=True)
    status = models.CharField(max_length=16, choices=STATUS_CHOICES, default=STATUS_DRAFT, db_index=True)
    is_public = models.BooleanField(default=True)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="created_listings")

    def __str__(self):
        return f"Listing #{self.id} - {self.title}"


class ScrapListingItem(TimestampedModel):
    listing = models.ForeignKey(ScrapListing, on_delete=models.CASCADE, related_name="items")
    material = models.ForeignKey(Material, on_delete=models.PROTECT)
    quantity_estimate = models.DecimalField(max_digits=12, decimal_places=3)
    unit = models.CharField(max_length=8, choices=Material.UNIT_CHOICES, default=Material.UNIT_KG)


class ScrapBid(TimestampedModel):
    STATUS_ACTIVE = "active"
    STATUS_RETRACTED = "retracted"
    STATUS_ACCEPTED = "accepted"
    STATUS_REJECTED = "rejected"
    STATUS_CHOICES = (
        (STATUS_ACTIVE, "Active"),
        (STATUS_RETRACTED, "Retracted"),
        (STATUS_ACCEPTED, "Accepted"),
        (STATUS_REJECTED, "Rejected"),
    )

    listing = models.ForeignKey(ScrapListing, on_delete=models.CASCADE, related_name="bids")
    bidder_org = models.ForeignKey(Organization, on_delete=models.PROTECT, related_name="bids")
    bidder_user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="bids")
    price_total = models.DecimalField(max_digits=14, decimal_places=2)
    message = models.TextField(blank=True)
    status = models.CharField(max_length=16, choices=STATUS_CHOICES, default=STATUS_ACTIVE)

    class Meta:
        indexes = [models.Index(fields=["listing", "price_total"])]


class ScrapAward(TimestampedModel):
    listing = models.OneToOneField(ScrapListing, on_delete=models.CASCADE, related_name="award")
    winning_bid = models.ForeignKey(ScrapBid, on_delete=models.PROTECT, related_name="awards")
    pickup = models.ForeignKey(PickupOrder, on_delete=models.SET_NULL, null=True, blank=True, related_name="awards")
    notes = models.TextField(blank=True)


class ScrapListingInvite(TimestampedModel):
    """Invite a specific organization (and optionally a user) to bid on a listing.

    When a listing is not public, only invited orgs may place bids.
    """

    listing = models.ForeignKey(ScrapListing, on_delete=models.CASCADE, related_name="invites")
    invited_org = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="listing_invites")
    invited_user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="listing_invites")

    class Meta:
        unique_together = ("listing", "invited_org")
