import hashlib
import secrets

from django.db import models


class City(models.Model):
    name = models.CharField(max_length=100, unique=True)
    region = models.CharField(max_length=150, blank=True, default="")
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "cities"
        ordering = ["name"]

    def __str__(self):
        return self.name


class Ward(models.Model):
    id = models.AutoField(primary_key=True)
    city = models.ForeignKey(City, related_name="wards", on_delete=models.CASCADE)
    ward = models.CharField(max_length=50)
    name = models.CharField(max_length=255)
    rank = models.PositiveIntegerField(default=0)
    score = models.DecimalField(max_digits=5, decimal_places=2, default=0)
    trend = models.DecimalField(max_digits=7, decimal_places=2, default=0)
    volunteers = models.PositiveIntegerField(default=0)
    kendras = models.PositiveIntegerField(default=0)
    partnerships = models.PositiveIntegerField(default=0)
    total_spots = models.PositiveIntegerField(default=0)
    photos = models.JSONField(default=list, blank=True)
    center = models.JSONField(default=list, blank=True)       # [longitude, latitude]
    boundary = models.JSONField(default=list, blank=True)     # [[longitude, latitude], ...]

    class Meta:
        db_table = "wards"
        ordering = ["rank", "ward"]
        constraints = [
            models.UniqueConstraint(fields=["city", "ward"], name="uq_ward_city_ward"),
        ]
        indexes = [models.Index(fields=["city", "rank"], name="idx_ward_city_rank")]

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


class Tag(models.Model):
    name = models.CharField(max_length=100, unique=True)

    class Meta:
        db_table = "tags"
        ordering = ["name"]

    def __str__(self):
        return self.name


class Spot(models.Model):
    id = models.AutoField(primary_key=True)
    KIND_CHOICES = [("spot", "Shahar Spot"), ("anchor", "Anchor Spot")]
    CATEGORY_CHOICES = [
        ("garbage", "Garbage"),
        ("potholes", "Potholes"),
        ("footpath", "Footpath"),
    ]
    STATUS_CHOICES = [
        ("reported", "Reported"),
        ("in-progress", "In Progress"),
        ("completed", "Completed"),
    ]

    ward = models.ForeignKey(Ward, related_name="spots", on_delete=models.CASCADE)
    spot_id = models.CharField(max_length=50, unique=True)  # external/source id
    spot = models.PositiveIntegerField()                    # human-facing spot number
    kind = models.CharField(max_length=20, choices=KIND_CHOICES, default="spot")
    category = models.CharField(max_length=30, choices=CATEGORY_CHOICES, null=True, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="reported")
    lat = models.DecimalField(max_digits=9, decimal_places=6)
    lon = models.DecimalField(max_digits=9, decimal_places=6)
    spot_name = models.CharField(max_length=255, blank=True, default="")
    address = models.CharField(max_length=500, blank=True, default="")
    score = models.DecimalField(max_digits=5, decimal_places=2, default=0)
    trend = models.DecimalField(max_digits=7, decimal_places=2, default=0)
    reported_date = models.DateField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    transformation_at = models.DateTimeField(null=True, blank=True)
    shortlisted = models.BooleanField(default=False)
    tags = models.ManyToManyField(Tag, related_name="spots", blank=True)

    class Meta:
        db_table = "spots"
        ordering = ["spot"]
        indexes = [
            models.Index(fields=["ward", "spot"], name="idx_spot_ward_number"),
            models.Index(fields=["kind", "shortlisted"], name="idx_spot_kind_shortlist"),
            models.Index(fields=["created_at"], name="idx_spot_created"),
            models.Index(fields=["status"], name="idx_spot_status"),
        ]

    def __str__(self):
        return self.spot_name or f"Spot {self.spot}"


class SpotImage(models.Model):
    IMAGE_TYPES = [
        ("reality", "Reality"),
        ("potential", "Potential"),
        ("transformation", "Transformation"),
    ]

    spot = models.ForeignKey(Spot, related_name="images", on_delete=models.CASCADE)
    image_type = models.CharField(max_length=20, choices=IMAGE_TYPES)
    url = models.URLField(max_length=1000)

    class Meta:
        db_table = "spot_images"
        constraints = [
            models.UniqueConstraint(fields=["spot", "image_type"], name="uq_spot_image_type"),
        ]


class Engagement(models.Model):
    ENGAGEMENT_TYPES = [
        ("citizen", "Citizen"),
        ("stakeholder", "Stakeholder"),
    ]

    city = models.ForeignKey(City, related_name="engagements", on_delete=models.CASCADE)
    spot = models.ForeignKey(Spot, related_name="engagements", on_delete=models.SET_NULL, null=True, blank=True)
    engagement_type = models.CharField(max_length=20, choices=ENGAGEMENT_TYPES, default="citizen")
    occurred_at = models.DateTimeField()

    class Meta:
        db_table = "engagements"
        indexes = [models.Index(fields=["city", "occurred_at"], name="idx_engagement_city_date")]


class APIKey(models.Model):
    PLAN_CHOICES = [("standard", "Standard"), ("enterprise", "Enterprise")]

    name = models.CharField(max_length=150)
    key_prefix = models.CharField(max_length=12, unique=True)
    key_hash = models.CharField(max_length=64, unique=True)
    plan = models.CharField(max_length=20, choices=PLAN_CHOICES, default="standard")
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    last_used_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        db_table = "api_keys"

    @staticmethod
    def hash_key(raw_key):
        return hashlib.sha256(raw_key.encode("utf-8")).hexdigest()

    @classmethod
    def create_key(cls, name, plan="standard"):
        raw = "shahar_" + secrets.token_urlsafe(32)
        obj = cls.objects.create(
            name=name,
            key_prefix=raw[:12],
            key_hash=cls.hash_key(raw),
            plan=plan,
        )
        return obj, raw
