"""
GigLez SQLAlchemy ORM Models

Database models matching the PostgreSQL + PostGIS schema
Based on Wigle wardriving patterns adapted for IoT RF device mapping
"""

import os
from datetime import datetime
from typing import Optional, List
from sqlalchemy import (
    Column, String, Integer, Float, DateTime, Text, Boolean,
    DECIMAL, ARRAY, ForeignKey, CheckConstraint, UniqueConstraint,
    Index, LargeBinary, JSON
)
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy.dialects.postgresql import JSONB, TSVECTOR

# =============================================================================
# DIALECT AWARENESS (PostgreSQL/PostGIS in prod, SQLite in dev)
# =============================================================================
# The production schema targets PostgreSQL + PostGIS, but dev/MVP must run on
# SQLite with no external services. We keep PostgreSQL-native behaviour on
# postgres via `.with_variant()` while degrading gracefully on SQLite.

_DATABASE_URL = os.getenv("DATABASE_URL", "")
IS_SQLITE = _DATABASE_URL.startswith("sqlite")

# JSON: JSONB on postgres, generic JSON (stored as TEXT) on sqlite
JSON_TYPE = JSON().with_variant(JSONB(), "postgresql")
# Text arrays: native ARRAY on postgres, JSON list on sqlite
STR_ARRAY_TYPE = JSON().with_variant(ARRAY(Text), "postgresql")
# Full-text search vector: TSVECTOR on postgres (trigger-populated), Text on sqlite
SEARCH_VECTOR_TYPE = Text().with_variant(TSVECTOR(), "postgresql")

if not IS_SQLITE:
    # geoalchemy2 is only importable/usable against a spatial backend
    from geoalchemy2 import Geometry
    from geoalchemy2.functions import ST_SetSRID, ST_MakePoint


# =============================================================================
# BASE
# =============================================================================

Base = declarative_base()


# =============================================================================
# CORE MODELS
# =============================================================================

class User(Base):
    """User accounts for community features"""

    __tablename__ = 'users'

    # Primary Key
    id = Column(Integer, primary_key=True)

    # Authentication
    username = Column(String(50), unique=True, nullable=False, index=True)
    email = Column(String(255), unique=True, nullable=False, index=True)
    password_hash = Column(String(255), nullable=False)

    # Profile
    display_name = Column(String(100))
    avatar_url = Column(Text)
    bio = Column(Text)

    # Statistics
    total_captures = Column(Integer, default=0)
    total_identifications = Column(Integer, default=0)
    reputation_score = Column(Integer, default=0)

    # Settings
    api_key = Column(String(64), unique=True, index=True)
    email_verified = Column(Boolean, default=False)

    # Timestamps
    created_at = Column(DateTime, default=datetime.utcnow)
    last_login = Column(DateTime)

    # Relationships
    sessions = relationship("Session", back_populates="user")
    captures = relationship("Capture", back_populates="user")
    identifications = relationship("Identification", back_populates="user", foreign_keys="[Identification.user_id]")
    votes = relationship("Vote", back_populates="user")

    def __repr__(self):
        return f"<User(id={self.id}, username='{self.username}')>"

    def to_dict(self):
        """Convert to dictionary"""
        return {
            'id': self.id,
            'username': self.username,
            'display_name': self.display_name,
            'total_captures': self.total_captures,
            'reputation_score': self.reputation_score,
            'created_at': self.created_at.isoformat() if self.created_at else None
        }


class Session(Base):
    """Wardriving/capture sessions (Wigle pattern)"""

    __tablename__ = 'sessions'

    # Primary Key
    id = Column(Integer, primary_key=True)

    # Foreign Keys
    user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), index=True)

    # Session Metadata
    session_uuid = Column(String(64), unique=True, nullable=False, index=True)
    name = Column(String(200))
    description = Column(Text)
    started_at = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
    ended_at = Column(DateTime)

    # Privacy Settings
    is_public = Column(Boolean, default=True, index=True)
    anonymize_gps = Column(Boolean, default=False)
    gps_precision_meters = Column(Integer, default=10)

    # Session Statistics (auto-updated by triggers)
    total_captures = Column(Integer, default=0)
    unique_devices = Column(Integer, default=0)
    distance_km = Column(DECIMAL(10, 2))

    # Bounding Box (auto-updated by triggers)
    min_latitude = Column(DECIMAL(10, 8))
    max_latitude = Column(DECIMAL(10, 8))
    min_longitude = Column(DECIMAL(11, 8))
    max_longitude = Column(DECIMAL(11, 8))

    # Relationships
    user = relationship("User", back_populates="sessions")
    captures = relationship("Capture", back_populates="session")

    def __repr__(self):
        return f"<Session(id={self.id}, uuid='{self.session_uuid}', name='{self.name}')>"

    def to_dict(self):
        """Convert to dictionary"""
        return {
            'id': self.id,
            'session_uuid': self.session_uuid,
            'name': self.name,
            'started_at': self.started_at.isoformat() if self.started_at else None,
            'ended_at': self.ended_at.isoformat() if self.ended_at else None,
            'total_captures': self.total_captures,
            'unique_devices': self.unique_devices,
            'is_public': self.is_public
        }


class Device(Base):
    """Known IoT device types from signature databases"""

    __tablename__ = 'devices'

    # Primary Key
    id = Column(Integer, primary_key=True)

    # Device Identification
    manufacturer = Column(String(200), index=True)
    model = Column(String(200))
    device_type = Column(String(100), index=True)
    description = Column(Text)

    # RF Characteristics
    typical_frequency = Column(Integer, index=True)
    frequency_range_low = Column(Integer)
    frequency_range_high = Column(Integer)
    modulation_types = Column(STR_ARRAY_TYPE)

    # Protocol Information
    protocol = Column(String(100), index=True)
    bit_length = Column(Integer)
    encoding = Column(String(50))

    # Metadata
    fcc_id = Column(String(50))
    manufacturer_code = Column(String(50))

    # Community Data
    created_at = Column(DateTime, default=datetime.utcnow)
    verified = Column(Boolean, default=False, index=True)
    verification_count = Column(Integer, default=0)

    # Source
    source = Column(String(50), index=True)  # 'flipper', 'rtl433', 'urh', 'community'
    source_url = Column(Text)

    # Full-text search (auto-updated by trigger on postgres)
    search_vector = Column('search_vector', SEARCH_VECTOR_TYPE, nullable=True)

    # Relationships
    signatures = relationship("Signature", back_populates="device")
    captures = relationship("Capture", back_populates="device")
    capture_matches = relationship("CaptureMatch", back_populates="device")

    def __repr__(self):
        return f"<Device(id={self.id}, manufacturer='{self.manufacturer}', model='{self.model}')>"

    def to_dict(self):
        """Convert to dictionary"""
        return {
            'id': self.id,
            'manufacturer': self.manufacturer,
            'model': self.model,
            'device_type': self.device_type,
            'description': self.description,
            'typical_frequency': self.typical_frequency,
            'protocol': self.protocol,
            'verified': self.verified,
            'source': self.source
        }


class Capture(Base):
    """RF signal captures with GPS coordinates"""

    __tablename__ = 'captures'

    # Primary Key: SHA256 hash of file (Wigle deduplication pattern)
    file_hash = Column(String(64), primary_key=True)

    # Foreign Keys
    session_id = Column(Integer, ForeignKey('sessions.id', ondelete='CASCADE'), index=True)
    user_id = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), index=True)
    device_id = Column(Integer, ForeignKey('devices.id', ondelete='SET NULL'), index=True)

    # GPS Data
    latitude = Column(DECIMAL(10, 8), nullable=False)
    longitude = Column(DECIMAL(11, 8), nullable=False)
    altitude = Column(DECIMAL(8, 2))
    gps_accuracy = Column(DECIMAL(6, 2))
    # Spatial geometry only exists on PostGIS; SQLite uses lat/lon + haversine
    if not IS_SQLITE:
        geom = Column(Geometry('POINT', srid=4326))  # Auto-populated by trigger

    # Timestamps
    captured_at = Column(DateTime, nullable=False, index=True)
    uploaded_at = Column(DateTime, default=datetime.utcnow, index=True)

    # RF Signal Data
    frequency = Column(Integer, nullable=False, index=True)
    rssi = Column(Integer)
    modulation = Column(String(50))
    preset = Column(String(100))

    # Protocol Information (if decoded)
    protocol = Column(String(100), index=True)
    bit_length = Column(Integer)
    key_data = Column(LargeBinary)
    timing_element = Column(Integer)

    # Raw Signal Data
    raw_data = Column(Text)
    raw_format = Column(String(20))  # 'RAW', 'BinRAW', 'KEY'

    # File Storage
    file_path = Column(String(500))
    file_size = Column(Integer)

    # Automatic Matching Results (best match)
    match_confidence = Column(DECIMAL(5, 4))
    match_method = Column(String(50))

    # Constraints
    __table_args__ = (
        CheckConstraint('latitude >= -90 AND latitude <= 90', name='valid_latitude'),
        CheckConstraint('longitude >= -180 AND longitude <= 180', name='valid_longitude'),
        CheckConstraint('match_confidence >= 0 AND match_confidence <= 1', name='valid_confidence'),
        CheckConstraint('frequency >= 300000000 AND frequency <= 928000000', name='valid_frequency'),
    ) + (
        # GiST spatial index only applies to the PostGIS geometry column
        (Index('idx_captures_geom', 'geom', postgresql_using='gist'),)
        if not IS_SQLITE else ()
    )

    # Relationships
    session = relationship("Session", back_populates="captures")
    user = relationship("User", back_populates="captures")
    device = relationship("Device", back_populates="captures")
    capture_matches = relationship("CaptureMatch", back_populates="capture")
    identifications = relationship("Identification", back_populates="capture")

    def __repr__(self):
        return f"<Capture(hash='{self.file_hash[:8]}...', freq={self.frequency}, protocol='{self.protocol}')>"

    def to_dict(self):
        """Convert to dictionary"""
        return {
            'file_hash': self.file_hash,
            'latitude': float(self.latitude) if self.latitude else None,
            'longitude': float(self.longitude) if self.longitude else None,
            'altitude': float(self.altitude) if self.altitude else None,
            'gps_accuracy': float(self.gps_accuracy) if self.gps_accuracy else None,
            'captured_at': self.captured_at.isoformat() if self.captured_at else None,
            'frequency': self.frequency,
            'rssi': self.rssi,
            'modulation': self.modulation,
            'protocol': self.protocol,
            'bit_length': self.bit_length,
            'device_id': self.device_id,
            'match_confidence': float(self.match_confidence) if self.match_confidence else None,
            'match_method': self.match_method
        }


class Signature(Base):
    """Protocol signatures for device matching"""

    __tablename__ = 'signatures'

    # Primary Key
    id = Column(Integer, primary_key=True)

    # Foreign Keys
    device_id = Column(Integer, ForeignKey('devices.id', ondelete='CASCADE'), index=True)

    # Signature Pattern
    protocol = Column(String(100), nullable=False, index=True)
    frequency = Column(Integer, index=True)
    modulation = Column(String(50))

    # Matching Criteria
    bit_pattern = Column(LargeBinary)
    bit_mask = Column(LargeBinary)
    timing_min = Column(Integer)
    timing_max = Column(Integer)

    # Raw Pattern
    pattern_regex = Column(Text)

    # Confidence Weighting
    weight = Column(DECIMAL(4, 3), default=1.0)

    # Metadata
    created_at = Column(DateTime, default=datetime.utcnow)
    source = Column(String(50), index=True)
    source_file = Column(String(500))

    # Constraints
    __table_args__ = (
        UniqueConstraint('device_id', 'protocol', 'bit_pattern', name='uq_device_protocol_pattern'),
    )

    # Relationships
    device = relationship("Device", back_populates="signatures")
    flipper_signature = relationship("FlipperSignature", back_populates="signature", uselist=False)
    rtl433_protocol = relationship("RTL433Protocol", back_populates="signature", uselist=False)

    def __repr__(self):
        return f"<Signature(id={self.id}, protocol='{self.protocol}', device_id={self.device_id})>"


class CaptureMatch(Base):
    """Many-to-many mapping of captures to devices with confidence scores"""

    __tablename__ = 'capture_matches'

    # Primary Key
    id = Column(Integer, primary_key=True)

    # Foreign Keys
    capture_id = Column(String(64), ForeignKey('captures.file_hash', ondelete='CASCADE'), index=True)
    device_id = Column(Integer, ForeignKey('devices.id', ondelete='CASCADE'), index=True)
    signature_id = Column(Integer, ForeignKey('signatures.id', ondelete='SET NULL'), index=True)

    # Match Details
    confidence = Column(DECIMAL(5, 4), nullable=False, index=True)
    match_method = Column(String(50), nullable=False)
    match_details = Column(JSON_TYPE)

    # Timestamp
    matched_at = Column(DateTime, default=datetime.utcnow)

    # Constraints
    __table_args__ = (
        CheckConstraint('confidence >= 0 AND confidence <= 1', name='valid_match_confidence'),
    )

    # Relationships
    capture = relationship("Capture", back_populates="capture_matches")
    device = relationship("Device", back_populates="capture_matches")

    def __repr__(self):
        return f"<CaptureMatch(capture='{self.capture_id[:8]}...', device={self.device_id}, confidence={self.confidence})>"


class Identification(Base):
    """User-submitted device identifications"""

    __tablename__ = 'identifications'

    # Primary Key
    id = Column(Integer, primary_key=True)

    # Foreign Keys
    capture_id = Column(String(64), ForeignKey('captures.file_hash', ondelete='CASCADE'), index=True)
    device_id = Column(Integer, ForeignKey('devices.id'), index=True)
    user_id = Column(Integer, ForeignKey('users.id'), index=True)

    # Identification Details
    confidence = Column(String(20))  # 'certain', 'likely', 'guess'
    notes = Column(Text)

    # Visual Evidence
    photo_urls = Column(STR_ARRAY_TYPE)

    # Community Validation
    upvotes = Column(Integer, default=0)
    downvotes = Column(Integer, default=0)
    verified = Column(Boolean, default=False, index=True)
    verified_by = Column(Integer, ForeignKey('users.id'))
    verified_at = Column(DateTime)

    # Timestamp
    submitted_at = Column(DateTime, default=datetime.utcnow)

    # Constraints
    __table_args__ = (
        UniqueConstraint('capture_id', 'user_id', 'device_id', name='uq_capture_user_device'),
    )

    # Relationships
    capture = relationship("Capture", back_populates="identifications")
    user = relationship("User", back_populates="identifications", foreign_keys=[user_id])
    votes = relationship("Vote", back_populates="identification")

    def __repr__(self):
        return f"<Identification(id={self.id}, capture='{self.capture_id[:8]}...', device={self.device_id})>"


class Vote(Base):
    """Votes on device identifications"""

    __tablename__ = 'votes'

    # Primary Key
    id = Column(Integer, primary_key=True)

    # Foreign Keys
    identification_id = Column(Integer, ForeignKey('identifications.id', ondelete='CASCADE'), index=True)
    user_id = Column(Integer, ForeignKey('users.id'), index=True)

    # Vote Data
    vote_type = Column(Integer, nullable=False)  # 1 = upvote, -1 = downvote
    voted_at = Column(DateTime, default=datetime.utcnow)

    # Constraints
    __table_args__ = (
        CheckConstraint('vote_type IN (1, -1)', name='valid_vote'),
        UniqueConstraint('identification_id', 'user_id', name='uq_identification_user'),
    )

    # Relationships
    identification = relationship("Identification", back_populates="votes")
    user = relationship("User", back_populates="votes")

    def __repr__(self):
        return f"<Vote(id={self.id}, identification={self.identification_id}, type={self.vote_type})>"


class UploadMarker(Base):
    """Track last uploaded capture per user/session (Wigle incremental sync pattern)"""

    __tablename__ = 'upload_markers'

    # Composite Primary Key
    user_id = Column(Integer, ForeignKey('users.id', ondelete='CASCADE'), primary_key=True)
    session_id = Column(Integer, ForeignKey('sessions.id', ondelete='CASCADE'), primary_key=True)

    # Marker Data
    last_capture_hash = Column(String(64))
    last_upload_time = Column(DateTime, nullable=False, default=datetime.utcnow)

    def __repr__(self):
        return f"<UploadMarker(user={self.user_id}, session={self.session_id})>"


# =============================================================================
# SIGNATURE DATABASE MODELS
# =============================================================================

class FlipperSignature(Base):
    """Flipper Zero specific signature data"""

    __tablename__ = 'flipper_signatures'

    # Primary Key
    id = Column(Integer, primary_key=True)

    # Foreign Key
    signature_id = Column(Integer, ForeignKey('signatures.id', ondelete='CASCADE'), index=True)

    # Flipper-Specific Fields
    filetype = Column(String(50))
    version = Column(Integer)
    preset = Column(String(100), index=True)

    # Custom Preset Data
    custom_preset_module = Column(String(50))
    custom_preset_data = Column(LargeBinary)

    # Protocol Data
    protocol = Column(String(100), index=True)
    bit = Column(Integer)
    key = Column(LargeBinary)
    te = Column(Integer)

    # RAW Data
    raw_data = Column(Text)
    bin_raw_bit = Column(Integer)
    bin_raw_te = Column(Integer)
    bin_raw_data = Column(LargeBinary)

    # Source
    source_file = Column(String(500))
    file_hash = Column(String(64), unique=True, index=True)  # SHA256 hash for deduplication
    file_path = Column(String(500))  # Relative path in dataset
    raw_format = Column(String(20))  # KEY, RAW, or BinRAW
    imported_at = Column(DateTime, default=datetime.utcnow)

    # Relationships
    signature = relationship("Signature", back_populates="flipper_signature")

    def __repr__(self):
        return f"<FlipperSignature(id={self.id}, protocol='{self.protocol}')>"


class RTL433Protocol(Base):
    """RTL_433 specific protocol data"""

    __tablename__ = 'rtl433_protocols'

    # Primary Key
    id = Column(Integer, primary_key=True)

    # Foreign Key
    signature_id = Column(Integer, ForeignKey('signatures.id', ondelete='CASCADE'), index=True)

    # Protocol Identification
    protocol_number = Column(Integer, index=True)
    protocol_name = Column(String(200))
    model = Column(String(200), index=True)

    # RF Characteristics
    frequency = Column(Integer)
    modulation = Column(String(50), index=True)

    # Timing Information
    short_width = Column(Integer)
    long_width = Column(Integer)
    reset_limit = Column(Integer)
    gap_limit = Column(Integer)

    # Decoding
    decoder_type = Column(String(50))
    bit_count = Column(Integer)

    # JSON Fields Mapping
    json_fields = Column(JSON_TYPE)

    # Source
    source_file = Column(String(500))
    imported_at = Column(DateTime, default=datetime.utcnow)

    # Relationships
    signature = relationship("Signature", back_populates="rtl433_protocol")

    def __repr__(self):
        return f"<RTL433Protocol(id={self.id}, name='{self.protocol_name}')>"


# =============================================================================
# HELPER FUNCTIONS
# =============================================================================

def create_all_tables(engine):
    """Create all tables in the database"""
    Base.metadata.create_all(engine)


def drop_all_tables(engine):
    """Drop all tables from the database (use with caution!)"""
    Base.metadata.drop_all(engine)
