from sqlalchemy import (
    Column, String, Integer, Numeric, Float, Text, Boolean, Date, DateTime,
    ForeignKey, CheckConstraint, UniqueConstraint, Enum as SQLEnum
)
from sqlalchemy.dialects.postgresql import UUID, JSONB, ARRAY
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.database import Base
import uuid
import enum


class DayOfWeek(enum.Enum):
    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 3
    THURSDAY = 4
    FRIDAY = 5
    SATURDAY = 6
    SUNDAY = 7


class MealType(enum.Enum):
    BREAKFAST = "breakfast"
    LUNCH = "lunch"
    DINNER = "dinner"


class MealPlanStatus(enum.Enum):
    DRAFT = "draft"
    PENDING_APPROVAL = "pending_approval"
    APPROVED = "approved"
    LOCKED = "locked"


class MealPlanItemStatus(str, enum.Enum):
    pending = "pending"
    approved = "approved"
    denied = "denied"
    swapped = "swapped"


class ApprovalTokenStatus(enum.Enum):
    ACTIVE = "active"
    USED = "used"
    EXPIRED = "expired"


class FamilyMemberRole(enum.Enum):
    ADULT = "adult"
    CHILD = "child"


class DenialReason(enum.Enum):
    TOO_EXPENSIVE = "too_expensive"
    BORING = "boring"
    DISLIKED_INGREDIENT = "disliked_ingredient"
    CULTURAL = "cultural"
    OTHER = "other"


class NeverSuggestReason(enum.Enum):
    ALLERGY = "allergy"
    DISLIKE = "dislike"
    TRIED_TOO_MUCH = "tried_too_much"
    OTHER = "other"


class ScrapeStatus(enum.Enum):
    STARTED = "started"
    SUCCESS = "success"
    FAILED = "failed"


class EmailStatus(enum.Enum):
    SENT = "sent"
    DELIVERED = "delivered"
    FAILED = "failed"
    BOUNCED = "bounced"


class IngredientMatchSource(enum.Enum):
    AUTO = "auto"
    AUTO_LLM = "auto_llm"
    MANUAL = "manual"


class FamilyProfile(Base):
    __tablename__ = "family_profile"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    name = Column(String(100), nullable=False)
    household_size = Column(Integer, nullable=False)
    adult_count = Column(Integer, nullable=False)
    child_count = Column(Integer, nullable=False)
    dietary_notes = Column(Text)
    budget_per_meal = Column(Numeric(10, 2), default=50.00)
    calorie_target = Column(Integer)
    pending_approval_policy = Column(String(10), nullable=False, server_default="approve")
    planner_config = Column(JSONB, nullable=True)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

    __table_args__ = (
        CheckConstraint("adult_count + child_count = household_size"),
        CheckConstraint("household_size > 0"),
        CheckConstraint("adult_count > 0"),
    )

    members = relationship("FamilyMember", back_populates="family_profile", cascade="all, delete-orphan")
    recipes = relationship("Recipe", back_populates="family_profile")
    meal_plans = relationship("MealPlan", back_populates="family_profile")
    pantry_items = relationship("HomePantry", back_populates="family_profile", cascade="all, delete-orphan")
    feedbacks = relationship("Feedback", back_populates="family_profile")
    never_suggests = relationship("NeverSuggest", back_populates="family_profile", cascade="all, delete-orphan")
    weekly_runs = relationship(
        "WeeklyRun", back_populates="family_profile", cascade="all, delete-orphan"
    )


class FamilyMember(Base):
    __tablename__ = "family_member"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    family_profile_id = Column(UUID(as_uuid=True), ForeignKey("family_profile.id", ondelete="CASCADE"))
    name = Column(String(100), nullable=False)
    email = Column(String(300))
    role = Column(SQLEnum(FamilyMemberRole, name="family_member_role_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]), nullable=False)
    likes_mushrooms = Column(Boolean, default=False)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

    __table_args__ = (
        UniqueConstraint("family_profile_id", "email"),
    )

    family_profile = relationship("FamilyProfile", back_populates="members")
    votes = relationship("MealPlanVote", back_populates="family_member", cascade="all, delete-orphan")
    feedbacks = relationship("Feedback", back_populates="family_member")


class Ingredient(Base):
    __tablename__ = "ingredient"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    name = Column(String(200), nullable=False)
    name_lower = Column(String(200), nullable=False, unique=True)
    plural_name = Column(String(200))
    aisle = Column(String(100))
    typical_price = Column(Numeric(10, 2))
    unit = Column(String(50))
    season_months = Column(ARRAY(Integer))
    aliases = Column(ARRAY(Text), nullable=False, server_default="{}")
    created_at = Column(DateTime(timezone=True), server_default=func.now())

    pantry_items = relationship("HomePantry", back_populates="ingredient")
    never_suggests = relationship("NeverSuggest", back_populates="ingredient")
    grocery_item_links = relationship("GroceryItem", back_populates="ingredient")
    matches = relationship("IngredientGroceryMatch", back_populates="ingredient", cascade="all, delete-orphan")


class Recipe(Base):
    __tablename__ = "recipe"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    family_profile_id = Column(UUID(as_uuid=True), ForeignKey("family_profile.id"))
    name = Column(String(300), nullable=False)
    description = Column(Text)
    image_url = Column(Text)
    image_source = Column(String(50))
    prep_time_minutes = Column(Integer)
    cook_time_minutes = Column(Integer)
    servings = Column(Integer, nullable=False)
    servings_scaled = Column(Integer)
    cuisine_tags = Column(ARRAY(String(50)))
    dietary_tags = Column(ARRAY(String(50)))
    protein_type = Column(String(50))
    spice_level = Column(Integer)
    calories_per_serving = Column(Integer)
    ingredients = Column(JSONB, nullable=False)
    side_dishes = Column(JSONB, default=list)
    instructions = Column(ARRAY(Text), nullable=False)
    source_url = Column(Text)
    scraped_at = Column(DateTime(timezone=True))
    is_manually_added = Column(Boolean, default=False)
    external_source = Column(String(50))
    external_id = Column(String(100))
    discovery_reason = Column(Text)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

    family_profile = relationship("FamilyProfile", back_populates="recipes")
    meal_plan_items = relationship("MealPlanItem", back_populates="recipe")

    @property
    def total_time_minutes(self):
        return (self.prep_time_minutes or 0) + (self.cook_time_minutes or 0)


class MealPlan(Base):
    __tablename__ = "meal_plan"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    family_profile_id = Column(UUID(as_uuid=True), ForeignKey("family_profile.id"))
    week_start_date = Column(Date, nullable=False)
    status = Column(SQLEnum(MealPlanStatus, name="meal_plan_status_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]), nullable=False, default=MealPlanStatus.DRAFT)
    approval_deadline = Column(DateTime(timezone=True))
    total_estimated_cost = Column(Numeric(10, 2))
    notes = Column(Text)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

    __table_args__ = (
        UniqueConstraint("family_profile_id", "week_start_date"),
    )

    family_profile = relationship("FamilyProfile", back_populates="meal_plans")
    items = relationship("MealPlanItem", back_populates="meal_plan", cascade="all, delete-orphan")


class MealPlanItem(Base):
    __tablename__ = "meal_plan_item"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    meal_plan_id = Column(UUID(as_uuid=True), ForeignKey("meal_plan.id", ondelete="CASCADE"))
    recipe_id = Column(UUID(as_uuid=True), ForeignKey("recipe.id"))
    day_of_week = Column(Integer, nullable=False)
    meal_type = Column(SQLEnum(MealType, name="meal_type_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]), nullable=False)
    approval_status = Column(SQLEnum(MealPlanItemStatus, name="meal_plan_item_status_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]), default=MealPlanItemStatus.pending)
    denial_reason = Column(SQLEnum(DenialReason, name="denial_reason_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]))
    denial_details = Column(Text)
    estimated_cost = Column(Numeric(10, 2))
    score = Column(Float)
    components = Column(JSONB)
    used_pantry_items = Column(ARRAY(UUID(as_uuid=True)))
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

    __table_args__ = (
        UniqueConstraint("meal_plan_id", "day_of_week", "meal_type"),
        CheckConstraint("day_of_week BETWEEN 1 AND 7"),
    )

    meal_plan = relationship("MealPlan", back_populates="items")
    recipe = relationship("Recipe", back_populates="meal_plan_items")
    feedback = relationship("Feedback", back_populates="meal_plan_item", uselist=False)
    votes = relationship("MealPlanVote", back_populates="meal_plan_item", cascade="all, delete-orphan")


class MealPlanVote(Base):
    __tablename__ = "meal_plan_vote"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    meal_plan_item_id = Column(UUID(as_uuid=True), ForeignKey("meal_plan_item.id", ondelete="CASCADE"))
    family_member_id = Column(UUID(as_uuid=True), ForeignKey("family_member.id", ondelete="CASCADE"))
    vote = Column(Boolean, nullable=False)
    voted_at = Column(DateTime(timezone=True), server_default=func.now())

    __table_args__ = (
        UniqueConstraint("meal_plan_item_id", "family_member_id"),
    )

    meal_plan_item = relationship("MealPlanItem", back_populates="votes")
    family_member = relationship("FamilyMember", back_populates="votes")


class ApprovalToken(Base):
    __tablename__ = "approval_token"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    meal_plan_item_id = Column(UUID(as_uuid=True), ForeignKey("meal_plan_item.id", ondelete="CASCADE"))
    family_member_id = Column(UUID(as_uuid=True), ForeignKey("family_member.id", ondelete="CASCADE"))
    token = Column(String(64), nullable=False, unique=True)
    status = Column(SQLEnum(ApprovalTokenStatus, name="approval_token_status_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]), default=ApprovalTokenStatus.ACTIVE)
    expires_at = Column(DateTime(timezone=True), nullable=False)
    used_at = Column(DateTime(timezone=True))
    created_at = Column(DateTime(timezone=True), server_default=func.now())

    __table_args__ = (
        UniqueConstraint("meal_plan_item_id", "family_member_id"),
    )

    meal_plan_item = relationship("MealPlanItem")
    family_member = relationship("FamilyMember")


class HomePantry(Base):
    __tablename__ = "home_pantry"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    family_profile_id = Column(UUID(as_uuid=True), ForeignKey("family_profile.id", ondelete="CASCADE"))
    ingredient_id = Column(UUID(as_uuid=True), ForeignKey("ingredient.id"))
    quantity = Column(Numeric(10, 2))
    unit = Column(String(50))
    expires_at = Column(Date)
    added_at = Column(DateTime(timezone=True), server_default=func.now())
    created_at = Column(DateTime(timezone=True), server_default=func.now())

    __table_args__ = (
        UniqueConstraint("family_profile_id", "ingredient_id"),
    )

    family_profile = relationship("FamilyProfile", back_populates="pantry_items")
    ingredient = relationship("Ingredient", back_populates="pantry_items")


class Feedback(Base):
    __tablename__ = "feedback"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    family_profile_id = Column(UUID(as_uuid=True), ForeignKey("family_profile.id"))
    family_member_id = Column(UUID(as_uuid=True), ForeignKey("family_member.id", ondelete="SET NULL"))
    meal_plan_item_id = Column(UUID(as_uuid=True), ForeignKey("meal_plan_item.id", ondelete="CASCADE"))
    rating = Column(Integer)
    never_suggest = Column(Boolean, default=False)
    denial_reason = Column(SQLEnum(DenialReason, name="denial_reason_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]))
    feedback_text = Column(Text)
    created_at = Column(DateTime(timezone=True), server_default=func.now())

    __table_args__ = (
        CheckConstraint("rating BETWEEN 1 AND 5"),
    )

    family_profile = relationship("FamilyProfile", back_populates="feedbacks")
    family_member = relationship("FamilyMember", back_populates="feedbacks")
    meal_plan_item = relationship("MealPlanItem", back_populates="feedback")


class NeverSuggest(Base):
    __tablename__ = "never_suggest"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    family_profile_id = Column(UUID(as_uuid=True), ForeignKey("family_profile.id", ondelete="CASCADE"))
    ingredient_id = Column(UUID(as_uuid=True), ForeignKey("ingredient.id", ondelete="CASCADE"))
    recipe_id = Column(UUID(as_uuid=True), ForeignKey("recipe.id", ondelete="CASCADE"))
    reason = Column(SQLEnum(NeverSuggestReason, name="never_suggest_reason_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]))
    notes = Column(Text)
    created_at = Column(DateTime(timezone=True), server_default=func.now())

    family_profile = relationship("FamilyProfile", back_populates="never_suggests")
    ingredient = relationship("Ingredient", back_populates="never_suggests")


class GroceryItem(Base):
    __tablename__ = "grocery_item"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    ingredient_id = Column(UUID(as_uuid=True), ForeignKey("ingredient.id", ondelete="SET NULL"))
    name = Column(String(300), nullable=False)
    brand = Column(String(200))
    current_price = Column(Numeric(10, 2))
    regular_price = Column(Numeric(10, 2))
    unit = Column(String(50))
    aisle = Column(String(100))
    image_url = Column(Text)
    product_url = Column(Text)
    description = Column(Text, nullable=True)
    is_on_sale = Column(Boolean, default=False)
    sale_start_date = Column(Date)
    sale_end_date = Column(Date)
    in_season = Column(Boolean, default=False)
    scraped_at = Column(DateTime(timezone=True), server_default=func.now())
    scraped_url = Column(Text)
    # R3-0: Swiftly API idempotency key. (source, external_id) → upsert.
    external_id = Column(String(100), nullable=True, index=True)
    source = Column(String(50), nullable=True)

    ingredient = relationship("Ingredient", back_populates="grocery_item_links")


class ScrapeLog(Base):
    __tablename__ = "scrape_log"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    source = Column(String(50), nullable=False)
    scrape_type = Column(String(50), nullable=False)
    status = Column(SQLEnum(ScrapeStatus, name="scrape_status_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]), nullable=False)
    items_scraped = Column(Integer, default=0)
    error_message = Column(Text)
    started_at = Column(DateTime(timezone=True), server_default=func.now())
    completed_at = Column(DateTime(timezone=True))
    duration_seconds = Column(Integer)


class EmailLog(Base):
    __tablename__ = "email_log"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    recipient_email = Column(String(300), nullable=False)
    recipient_name = Column(String(200))
    template = Column(String(100), nullable=False)
    meal_plan_id = Column(UUID(as_uuid=True), ForeignKey("meal_plan.id"))
    meal_plan_item_id = Column(UUID(as_uuid=True), ForeignKey("meal_plan_item.id"))
    sendgrid_message_id = Column(String(100))
    status = Column(SQLEnum(EmailStatus, name="email_status_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]), nullable=False)
    error_message = Column(Text)
    created_at = Column(DateTime(timezone=True), server_default=func.now())
    delivered_at = Column(DateTime(timezone=True))

    meal_plan = relationship("MealPlan")
    meal_plan_item = relationship("MealPlanItem")


class IngredientGroceryMatch(Base):
    __tablename__ = "ingredient_grocery_match"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    ingredient_id = Column(UUID(as_uuid=True), ForeignKey("ingredient.id", ondelete="CASCADE"), nullable=False)
    grocery_item_id = Column(UUID(as_uuid=True), ForeignKey("grocery_item.id", ondelete="CASCADE"), nullable=False)
    confidence = Column(Numeric(4, 3), nullable=False)
    source = Column(SQLEnum(IngredientMatchSource, name="ingredient_match_source_enum", create_type=False, values_callable=lambda obj: [e.value for e in obj]), nullable=False)
    updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

    __table_args__ = (
        UniqueConstraint("ingredient_id", "grocery_item_id", name="uq_ingredient_grocery_match_pair"),
    )

    ingredient = relationship("Ingredient", back_populates="matches")
    grocery_item = relationship("GroceryItem")


class WeeklyRun(Base):
    __tablename__ = "weekly_run"

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    family_id = Column(
        UUID(as_uuid=True),
        ForeignKey("family_profile.id", ondelete="CASCADE"),
        nullable=False,
    )
    week_start_date = Column(Date, nullable=False)
    status = Column(String(20), nullable=False, default="pending")
    scraped_at = Column(DateTime(timezone=True))
    generated_at = Column(DateTime(timezone=True))
    emailed_at = Column(DateTime(timezone=True))
    reminded_at = Column(DateTime(timezone=True))
    deadline_passed_at = Column(DateTime(timezone=True))
    finalized_at = Column(DateTime(timezone=True))
    feedback_analysis = Column(JSONB, nullable=True)
    used_stale_data = Column(Boolean, nullable=False, default=False)
    error_step = Column(String(50))
    error_message = Column(Text)
    created_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())

    __table_args__ = (
        UniqueConstraint(
            "family_id", "week_start_date", name="uq_weekly_run_family_week"
        ),
    )

    family_profile = relationship("FamilyProfile", back_populates="weekly_runs")
