from sqlmodel import SQLModel, Field, JSON, Column
from typing import Dict, Any, Optional
from datetime import datetime, timezone
from enum import Enum
import uuid
from sqlalchemy import Boolean
from fastapi_users_db_sqlalchemy import SQLAlchemyUserDatabase
from fastapi_users import schemas


class User(SQLModel, table=True):
    """Database model for users"""
    __tablename__ = "user"
    
    id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)
    email: str = Field(unique=True, index=True, description="User's email address")
    hashed_password: str = Field(description="Bcrypt hashed password")
    name: Optional[str] = Field(default=None, description="User's display name")
    is_active: bool = Field(default=True, description="Whether the user account is active")
    is_verified: bool = Field(default=False, description="Whether the user's email is verified")
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), description="When the user account was created")


# FastAPI Users schemas - must inherit from fastapi_users.schemas
class UserRead(schemas.BaseUser[str]):
    """Model for reading user data"""
    name: Optional[str] = None
    created_at: datetime


class UserCreate(schemas.BaseUserCreate):
    """Model for creating new users"""
    name: Optional[str] = None


class UserUpdate(schemas.BaseUserUpdate):
    """Model for updating user data"""
    name: Optional[str] = None


class BillingInterval(str, Enum):
    """Billing interval for license purchases"""
    MONTHLY = "monthly"
    ANNUAL = "annual"
    LIFETIME = "lifetime"


class LicenseStatus(str, Enum):
    """Status of a license purchase"""
    ACTIVE = "active"
    EXPIRED = "expired"
    CANCELLED = "cancelled"
    SUSPENDED = "suspended"


class ScanType(str, Enum):
    """Enum for different types of scans"""
    DOMAIN = "domain"
    QUANTUM = "quantum"
    PORT = "port"
    CODE_ANALYSIS = "code_analysis"
    CONFIG = "config"
    BINARY = "binary"
    UNIFIED = "unified"


class PatchType(str, Enum):
    """Enum for different types of patches"""
    SCANNER = "scanner"
    AI = "ai"


class PatchStatus(str, Enum):
    """Enum for patch lifecycle status"""
    PENDING = "pending"
    APPLIED = "applied"
    ROLLED_BACK = "rolled_back"
    STALE = "stale"



class ScanRecord(SQLModel, table=True):
    """Database model for storing scan records"""
    __tablename__ = "scan_records"

    id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)
    target: str = Field(index=True, description="Target domain/IP that was scanned")
    project_path: Optional[str] = Field(default=None, index=True, description="Project root path for code scans")
    scan_type: ScanType = Field(index=True, description="Type of scan performed")
    timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), index=True, description="When the scan was performed")
    user_id: str = Field(index=True, description="User who created this scan")
    scan_data: Dict[str, Any] = Field(sa_column=Column(JSON), description="Raw scan results as JSON")

    class Config:
        arbitrary_types_allowed = True


class ScanFile(SQLModel, table=True):
    """Database model for storing individual scanned files (clean files list)
    
    This table stores the list of clean files separately from the main scan record
    to reduce the size of scan_data JSON and improve performance.
    """
    __tablename__ = "scan_files"

    id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)
    scan_id: str = Field(index=True, foreign_key="scan_records.id", description="Reference to parent scan record")
    file_path: str = Field(description="Absolute path to the scanned file")
    has_findings: bool = Field(default=False, index=True, description="Whether this file has any findings")
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), description="When the record was created")

    class Config:
        arbitrary_types_allowed = True


# Pydantic models for API responses (following FastAPI tutorial pattern)
class ScanRecordRead(SQLModel):
    """Model for reading scan record items"""
    id: str
    target: str
    project_path: Optional[str] = None
    scan_type: ScanType
    timestamp: datetime
    user_id: str
    scan_data: Dict[str, Any]


class ScanRecordCreate(SQLModel):
    """Model for creating new scan record items"""
    target: str
    scan_type: ScanType
    scan_data: Dict[str, Any]


class ScanRecordResponse(SQLModel):
    """Response model for scan record API endpoints"""
    success: bool
    data: list[ScanRecordRead]
    message: Optional[str] = None


class PatchRecord(SQLModel, table=True):
    """Database model for storing patch records"""
    __tablename__ = "patch_records"

    # Primary identification
    id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)
    user_id: str = Field(index=True, description="User who owns this patch")
    file_path: str = Field(index=True, description="Absolute path to the file this patch targets")
    finding_fingerprint: str = Field(index=True, description="Unique fingerprint of the finding")

    # Patch content
    patch_diff: str = Field(description="Git unified diff format patch")
    patch_type: PatchType = Field(description="Source of the patch (scanner or ai)")

    # State tracking
    status: PatchStatus = Field(default=PatchStatus.PENDING, index=True, description="Current lifecycle status")
    content_hash: str = Field(description="SHA256 hash of file content when patch was generated")

    # Metadata
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), index=True, description="When the patch was created")
    applied_at: Optional[datetime] = Field(default=None, description="When the patch was applied")

    # Statistics
    lines_added: int = Field(default=0, description="Number of lines added by this patch")
    lines_removed: int = Field(default=0, description="Number of lines removed by this patch")
    hunks_count: int = Field(default=0, description="Number of hunks in this patch")

    # Source reference
    source_data: Dict[str, Any] = Field(sa_column=Column(JSON), description="Finding details, check_id, severity, etc.")

    class Config:
        arbitrary_types_allowed = True


class PatchRecordRead(SQLModel):
    """Model for reading patch record items"""
    id: str
    user_id: str
    file_path: str
    finding_fingerprint: str
    patch_diff: str
    patch_type: PatchType
    status: PatchStatus
    content_hash: str
    created_at: datetime
    applied_at: Optional[datetime]
    lines_added: int
    lines_removed: int
    hunks_count: int
    source_data: Dict[str, Any]


class PatchRecordCreate(SQLModel):
    """Model for creating new patch records"""
    file_path: str
    finding_fingerprint: str
    patch_diff: str
    patch_type: PatchType
    content_hash: str
    lines_added: int = 0
    lines_removed: int = 0
    hunks_count: int = 0
    source_data: Dict[str, Any]


class PatchRecordResponse(SQLModel):
    """Response model for patch record API endpoints"""
    success: bool
    data: list[PatchRecordRead]
    message: Optional[str] = None


class LicenseRecord(SQLModel, table=True):
    """Database model for tracking license purchases with price snapshots.

    Captures the price at time of purchase so pricing changes and promotions
    don't affect existing customers. Tracks validity windows for annual licenses.
    """
    __tablename__ = "license_records"

    id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)
    user_id: str = Field(index=True, description="User who owns this license")
    license_key: str = Field(index=True, description="License key issued")

    # Tier and pricing snapshot (frozen at purchase time)
    tier: str = Field(index=True, description="Tier at purchase: trial, professional, enterprise")
    price_at_purchase: Optional[float] = Field(default=None, description="Price paid (None for trial/enterprise)")
    billing_interval: BillingInterval = Field(description="Monthly, annual, or lifetime")
    currency: str = Field(default="USD", description="Currency code")

    # Validity window
    validity_start: datetime = Field(description="When the license period starts")
    validity_end: Optional[datetime] = Field(default=None, description="When the license period ends (None = perpetual)")

    # Purchase metadata
    purchased_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), index=True, description="When the purchase was made")
    status: LicenseStatus = Field(default=LicenseStatus.ACTIVE, index=True, description="Current license status")
    seats: int = Field(default=1, description="Number of seats purchased")

    # Promotion tracking
    promotion_code: Optional[str] = Field(default=None, description="Promo/discount code used (if any)")
    original_price: Optional[float] = Field(default=None, description="Original price before discount")

    # Features snapshot (frozen at purchase time)
    features_snapshot: Dict[str, Any] = Field(sa_column=Column(JSON), description="Features enabled at purchase time")

    # Notes (for enterprise/manual sales)
    notes: Optional[str] = Field(default=None, description="Internal notes (PO number, sales rep, etc.)")

    class Config:
        arbitrary_types_allowed = True


class LicenseRecordRead(SQLModel):
    """Model for reading license records"""
    id: str
    user_id: str
    license_key: str
    tier: str
    price_at_purchase: Optional[float]
    billing_interval: BillingInterval
    currency: str
    validity_start: datetime
    validity_end: Optional[datetime]
    purchased_at: datetime
    status: LicenseStatus
    seats: int
    promotion_code: Optional[str]
    original_price: Optional[float]
    features_snapshot: Dict[str, Any]
    notes: Optional[str]


class LicenseRecordCreate(SQLModel):
    """Model for creating license purchase records"""
    license_key: str
    tier: str
    billing_interval: BillingInterval
    price_at_purchase: Optional[float] = None
    currency: str = "USD"
    validity_start: Optional[datetime] = None
    validity_end: Optional[datetime] = None
    seats: int = 1
    promotion_code: Optional[str] = None
    notes: Optional[str] = None


class GeneratedReport(SQLModel, table=True):
    """Database model for AI-generated executive reports."""
    __tablename__ = "generated_reports"

    id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)
    user_id: str = Field(index=True)
    title: str = Field(description="Auto-generated title from first heading or timestamp")
    user_context: Optional[str] = Field(default=None, description="User-provided context used to generate the report")
    content: str = Field(description="Full Markdown report content")
    scan_ids: str = Field(description="JSON array of scan IDs included in this report")
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), index=True)

    class Config:
        arbitrary_types_allowed = True


class FileScanCache(SQLModel, table=True):
    """Per-file incremental scan cache.

    Stores the last-known findings for each (project_root, file_path, language) tuple so
    subsequent scans can skip files whose content and rules have not changed.

    Cache hit: mtime matches (fast path) OR sha256 matches (content unchanged despite mtime change).
    Cache invalidation: rule_fingerprint changes (rules updated) OR file content changes.
    """
    __tablename__ = "file_scan_cache"

    # Primary key: sha256(project_root|file_path|language)
    id: str = Field(primary_key=True)
    project_root: str = Field(index=True, description="Normalised absolute project root path")
    file_path: str = Field(index=True, description="Normalised absolute file path")
    # language includes supplementary scanner variants: 'java', 'java_bouncycastle', 'python_paramiko', …
    language: str = Field(description="Language (or scanner variant) used to produce these findings")
    mtime: float = Field(description="File mtime at cache write time (fast-path comparison)")
    file_hash: str = Field(description="SHA-256 of file content at cache write time")
    rule_fingerprint: str = Field(description="SHA-256 of all rule inputs for this language/scanner")
    findings_json: str = Field(default="[]", description="JSON-serialised list of raw findings for this file")
    cached_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="When this cache entry was last written",
    )

    class Config:
        arbitrary_types_allowed = True


class TrialSignup(SQLModel, table=True):
    """
    Tracks every trial license issued.

    Dual purpose:
      1. Abuse prevention — one trial per email address (enforced on insert).
      2. Lead capture    — name / company are stored here for sales/marketing.

    The ``synced_at`` column is NULL until the record has been successfully
    forwarded to the external CRM / cerebion_web webhook.  A background task
    (or manual script) can query ``WHERE synced_at IS NULL`` to replay any
    rows that were created while the webhook was down.
    """
    __tablename__ = "trial_signups"

    id: str = Field(default_factory=lambda: str(uuid.uuid4()), primary_key=True)

    # Identity (email is the uniqueness key)
    email:   str = Field(index=True, unique=True, description="Work email — one trial per address")
    name:    str = Field(description="Customer full name")
    company: str = Field(description="Company / organisation")

    # License info
    license_key:  str = Field(description="Issued trial license key")
    license_file: str = Field(description="Absolute path to the signed offline license JSON")
    expires_at:   datetime = Field(description="Trial expiry (UTC)")

    # Timestamps
    created_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        index=True,
        description="When the trial was issued (UTC)",
    )

    # CRM / webhook sync
    synced_at: Optional[datetime] = Field(
        default=None,
        description="When this lead was successfully forwarded to cerebion_web (NULL = pending)",
    )
    sync_error: Optional[str] = Field(
        default=None,
        description="Last sync error message, if any",
    )

    class Config:
        arbitrary_types_allowed = True
