from typing import List, Dict, Optional, Any, Union, Literal, TypeVar, Generic, Annotated
from pydantic import BaseModel, Field, EmailStr, HttpUrl, model_validator, field_validator # type: ignore
from pydantic.types import constr, conint # type: ignore
from datetime import datetime, date, UTC
from enum import Enum
import uuid
from auth_utils import (
    AuthenticationManager, 
    validate_password_strength, 
    sanitize_login_input,
    SecurityConfig
)

# Generic type variable
T = TypeVar('T')

# ============================
# Enums
# ============================

class UserType(str, Enum):
    CANDIDATE = "candidate"
    EMPLOYER = "employer"
    GUEST = "guest"

class UserGender(str, Enum):
    FEMALE = "female"
    MALE = "male"

class UserStatus(str, Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"
    PENDING = "pending"
    BANNED = "banned"

class SkillLevel(str, Enum):
    BEGINNER = "beginner"
    INTERMEDIATE = "intermediate"
    ADVANCED = "advanced"
    EXPERT = "expert"

class EmploymentType(str, Enum):
    FULL_TIME = "full-time"
    PART_TIME = "part-time"
    CONTRACT = "contract"
    INTERNSHIP = "internship"
    FREELANCE = "freelance"

class InterviewType(str, Enum):
    PHONE = "phone"
    VIDEO = "video"
    ONSITE = "onsite"
    TECHNICAL = "technical"
    BEHAVIORAL = "behavioral"

class ApplicationStatus(str, Enum):
    APPLIED = "applied"
    REVIEWING = "reviewing"
    INTERVIEW = "interview"
    OFFER = "offer"
    REJECTED = "rejected"
    ACCEPTED = "accepted"
    WITHDRAWN = "withdrawn"

class InterviewRecommendation(str, Enum):
    STRONG_HIRE = "strong_hire"
    HIRE = "hire"
    NO_HIRE = "no_hire"
    STRONG_NO_HIRE = "strong_no_hire"

class ChatSenderType(str, Enum):
    USER = "user"
    ASSISTANT = "assistant"
    # Frontend can use this to set mock responses
    SYSTEM = "system"
    INFORMATION = "information"
    WARNING = "warning"
    ERROR = "error"

class Requirements(BaseModel):
    required: List[str] = Field(default_factory=list)
    preferred: List[str] = Field(default_factory=list)

    @model_validator(mode='before')
    def validate_requirements(cls, values):
        if not isinstance(values, dict):
            raise ValueError("Requirements must be a dictionary with 'required' and 'preferred' keys.")
        return values

class Citation(BaseModel):
    text: str
    source: str
    context: str
    relevance: int # 0-100 scale

class SkillStatus(str, Enum):
    PENDING = "pending"
    COMPLETE = "complete"
    WAITING = "waiting"
    ERROR = "error"

class SkillMatch(BaseModel):
    requirement: str
    domain: str
    status: SkillStatus
    match_score: int = Field(..., alias='matchScore')
    assessment: str
    citations: List[Citation] = Field(default_factory=list)
    description: str
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }
    
class ApiMessageType(str, Enum):
    BINARY = "binary"
    TEXT = "text"
    JSON = "json"

class ApiStatusType(str, Enum):
    STREAMING = "streaming"
    STATUS = "status"
    DONE = "done"
    ERROR = "error"

class ChatContextType(str, Enum):
    JOB_SEARCH = "job_search"
    JOB_REQUIREMENTS = "job_requirements"
    CANDIDATE_CHAT = "candidate_chat"
    INTERVIEW_PREP = "interview_prep"
    RESUME_REVIEW = "resume_review"
    GENERAL = "general"
    GENERATE_PERSONA = "generate_persona"
    GENERATE_PROFILE = "generate_profile"
    GENERATE_IMAGE = "generate_image"
    RAG_SEARCH = "rag_search"
    SKILL_MATCH = "skill_match"

class AIModelType(str, Enum):
    QWEN2_5 = "qwen2.5"
    FLUX_SCHNELL = "flux-schnell"

class MFAMethod(str, Enum):
    APP = "app"
    SMS = "sms"
    EMAIL = "email"

class VectorStoreType(str, Enum):
    CHROMA = "chroma",
    # FAISS = "faiss",
    # PINECONE = "pinecone"
    # QDRANT = "qdrant"
    # FAISS = "faiss"
    # MILVUS = "milvus"
    # WEAVIATE = "weaviate"

class DataSourceType(str, Enum):
    DOCUMENT = "document"
    WEBSITE = "website"
    API = "api"
    DATABASE = "database"
    INTERNAL = "internal"

class ProcessingStepType(str, Enum):
    EXTRACT = "extract"
    TRANSFORM = "transform"
    CHUNK = "chunk"
    EMBED = "embed"
    FILTER = "filter"
    SUMMARIZE = "summarize"

class SearchType(str, Enum):
    SIMILARITY = "similarity"
    MMR = "mmr"
    HYBRID = "hybrid"
    KEYWORD = "keyword"

class ActivityType(str, Enum):
    LOGIN = "login"
    SEARCH = "search"
    VIEW_JOB = "view_job"
    APPLY_JOB = "apply_job"
    MESSAGE = "message"
    UPDATE_PROFILE = "update_profile"
    CHAT = "chat"

class ThemePreference(str, Enum):
    LIGHT = "light"
    DARK = "dark"
    SYSTEM = "system"

class NotificationType(str, Enum):
    EMAIL = "email"
    PUSH = "push"
    IN_APP = "in_app"

class FontSize(str, Enum):
    SMALL = "small"
    MEDIUM = "medium"
    LARGE = "large"

class SalaryPeriod(str, Enum):
    HOUR = "hour"
    DAY = "day"
    MONTH = "month"
    YEAR = "year"

class LanguageProficiency(str, Enum):
    BASIC = "basic"
    CONVERSATIONAL = "conversational"
    FLUENT = "fluent"
    NATIVE = "native"

class SocialPlatform(str, Enum):
    LINKEDIN = "linkedin"
    TWITTER = "twitter"
    GITHUB = "github"
    DRIBBBLE = "dribbble"
    BEHANCE = "behance"
    WEBSITE = "website"
    OTHER = "other"

class ColorBlindMode(str, Enum):
    PROTANOPIA = "protanopia"
    DEUTERANOPIA = "deuteranopia"
    TRITANOPIA = "tritanopia"
    NONE = "none"

class SortOrder(str, Enum):
    ASC = "asc"
    DESC = "desc"


class LoginRequest(BaseModel):
    login: str  # Can be email or username
    password: str
    
    @field_validator('login')
    def sanitize_login(cls, v):
        return sanitize_login_input(v)
    
    @field_validator('password')
    def validate_password_not_empty(cls, v):
        if not v or not v.strip():
            raise ValueError('Password cannot be empty')
        return v

# ============================
# MFA Models
# ============================

class EmailVerificationRequest(BaseModel):
    token: str

class MFARequest(BaseModel):
    username: str
    password: str
    device_id: str = Field(..., alias="deviceId")
    device_name: str = Field(..., alias="deviceName")
    model_config = {
        "populate_by_name": True,   # Allow both field names and aliases
    }

class MFAVerifyRequest(BaseModel):
    email: EmailStr
    code: str
    device_id: str = Field(..., alias="deviceId")
    remember_device: bool = Field(False, alias="rememberDevice")
    model_config = {
        "populate_by_name": True,   # Allow both field names and aliases
    }

class MFAData(BaseModel):
    message: str
    device_id: str = Field(..., alias="deviceId")
    device_name: str = Field(..., alias="deviceName")
    code_sent: str = Field(..., alias="codeSent")
    email: str
    model_config = {
        "populate_by_name": True,   # Allow both field names and aliases
    }

class MFARequestResponse(BaseModel):
    mfa_required: bool = Field(..., alias="mfaRequired")
    mfa_data: Optional[MFAData] = Field(None, alias="mfaData")
    model_config = {
        "populate_by_name": True,   # Allow both field names and aliases
    }

class ResendVerificationRequest(BaseModel):
    email: EmailStr

# ============================
# Supporting Models
# ============================

class Tunables(BaseModel):
    enable_rag: bool = Field(True, alias="enableRAG")
    enable_tools: bool = Field(True, alias="enableTools")
    enable_context: bool = Field(True, alias="enableContext")

class CandidateQuestion(BaseModel):
    question: str
    tunables: Optional[Tunables] = None

class Location(BaseModel):
    city: str
    state: Optional[str] = None
    country: str
    postal_code: Optional[str] = Field(None, alias="postalCode")
    latitude: Optional[float] = None
    longitude: Optional[float] = None
    remote: Optional[bool] = None
    hybrid_options: Optional[List[str]] = Field(None, alias="hybridOptions")
    address: Optional[str] = None

class Skill(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    name: str
    category: str
    level: SkillLevel
    years_of_experience: Optional[int] = Field(None, alias="yearsOfExperience")

class WorkExperience(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    company_name: str = Field(..., alias="companyName")
    position: str
    start_date: datetime = Field(..., alias="startDate")
    end_date: Optional[datetime] = Field(None, alias="endDate")
    is_current: bool = Field(..., alias="isCurrent")
    description: str
    skills: List[str]
    location: Location
    achievements: Optional[List[str]] = None

class Education(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    institution: str
    degree: str
    field_of_study: str = Field(..., alias="fieldOfStudy")
    start_date: datetime = Field(..., alias="startDate")
    end_date: Optional[datetime] = Field(None, alias="endDate")
    is_current: bool = Field(..., alias="isCurrent")
    gpa: Optional[float] = None
    achievements: Optional[List[str]] = None
    location: Optional[Location] = None

class Language(BaseModel):
    language: str
    proficiency: LanguageProficiency

class Certification(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    name: str
    issuing_organization: str = Field(..., alias="issuingOrganization")
    issue_date: datetime = Field(..., alias="issueDate")
    expiration_date: Optional[datetime] = Field(None, alias="expirationDate")
    credential_id: Optional[str] = Field(None, alias="credentialId")
    credential_url: Optional[HttpUrl] = Field(None, alias="credentialUrl")

class SocialLink(BaseModel):
    platform: SocialPlatform
    url: HttpUrl

class DesiredSalary(BaseModel):
    amount: float
    currency: str
    period: SalaryPeriod

class SalaryRange(BaseModel):
    min: float
    max: float
    currency: str
    period: SalaryPeriod
    is_visible: bool = Field(..., alias="isVisible")

class PointOfContact(BaseModel):
    name: str
    position: str
    email: EmailStr
    phone: Optional[str] = None

class RefreshToken(BaseModel):
    token: str
    expires_at: datetime = Field(..., alias="expiresAt")
    device: str
    ip_address: str = Field(..., alias="ipAddress")
    is_revoked: bool = Field(..., alias="isRevoked")
    revoked_reason: Optional[str] = Field(None, alias="revokedReason")

class Attachment(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    file_name: str = Field(..., alias="fileName")
    file_type: str = Field(..., alias="fileType")
    file_size: int = Field(..., alias="fileSize")
    file_url: str = Field(..., alias="fileUrl")
    uploaded_at: datetime = Field(..., alias="uploadedAt")
    is_processed: bool = Field(..., alias="isProcessed")
    processing_result: Optional[Any] = Field(None, alias="processingResult")
    thumbnail_url: Optional[str] = Field(None, alias="thumbnailUrl")

class MessageReaction(BaseModel):
    user_id: str = Field(..., alias="userId")
    reaction: str
    timestamp: datetime

class EditHistory(BaseModel):
    content: str
    edited_at: datetime = Field(..., alias="editedAt")
    edited_by: str = Field(..., alias="editedBy")

class CustomQuestion(BaseModel):
    question: str
    answer: str

class CandidateContact(BaseModel):
    email: EmailStr
    phone: Optional[str] = None

class ApplicationDecision(BaseModel):
    status: Literal["accepted", "rejected"]
    reason: Optional[str] = None
    date: datetime
    by: str

class SkillAssessment(BaseModel):
    skill_name: str = Field(..., alias="skillName")
    score: Annotated[float, Field(ge=0, le=10)]
    comments: Optional[str] = None

class NotificationPreference(BaseModel):
    type: NotificationType
    events: List[str]
    is_enabled: bool = Field(..., alias="isEnabled")

class AccessibilitySettings(BaseModel):
    font_size: FontSize = Field(..., alias="fontSize")
    high_contrast: bool = Field(..., alias="highContrast")
    reduce_motion: bool = Field(..., alias="reduceMotion")
    screen_reader: bool = Field(..., alias="screenReader")
    color_blind_mode: Optional[ColorBlindMode] = Field(None, alias="colorBlindMode")

class ProcessingStep(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    type: ProcessingStepType
    parameters: Dict[str, Any]
    order: int
    depends_on: Optional[List[str]] = Field(None, alias="dependsOn")

class RetrievalParameters(BaseModel):
    search_type: SearchType = Field(..., alias="searchType")
    top_k: int = Field(..., alias="topK")
    similarity_threshold: Optional[float] = Field(None, alias="similarityThreshold")
    reranker_model: Optional[str] = Field(None, alias="rerankerModel")
    use_keyword_boost: bool = Field(..., alias="useKeywordBoost")
    filter_options: Optional[Dict[str, Any]] = Field(None, alias="filterOptions")
    context_window: int = Field(..., alias="contextWindow")

class ErrorDetail(BaseModel):
    code: str
    message: str
    details: Optional[Any] = None

# ============================
# Main Models
# ============================

# Base user model without user_type field
class BaseUser(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    email: EmailStr
    first_name: str = Field(..., alias="firstName")
    last_name: str = Field(..., alias="lastName")
    full_name: str = Field(..., alias="fullName")
    phone: Optional[str] = None
    location: Optional[Location] = None
    created_at: datetime = Field(..., alias="createdAt")
    updated_at: datetime = Field(..., alias="updatedAt")
    last_login: Optional[datetime] = Field(None, alias="lastLogin")
    profile_image: Optional[str] = Field(None, alias="profileImage")
    status: UserStatus
    is_admin: bool = Field(default=False, alias="isAdmin")
    
    model_config = {
        "populate_by_name": True,   # Allow both field names and aliases
        "use_enum_values": True     # Use enum values instead of names
    }

# Generic base user with user_type for API responses
class BaseUserWithType(BaseUser):
    user_type: UserType = Field(..., alias="userType")

class RagEntry(BaseModel):
    name: str
    description: str = ""
    enabled: bool = True

class RagContentMetadata(BaseModel):
    source_file: str = Field(..., alias="sourceFile")
    line_begin: int = Field(..., alias="lineBegin")
    line_end: int = Field(..., alias="lineEnd")
    lines: int
    chunk_begin: Optional[int] = Field(None, alias="chunkBegin")
    chunk_end: Optional[int] = Field(None, alias="chunkEnd")
    metadata: Dict[str, Any] = Field(default_factory=dict)
    model_config = {
        "populate_by_name": True,   # Allow both field names and aliases
    }

class RagContentResponse(BaseModel):
    id: str
    content: str
    metadata: RagContentMetadata

class DocumentType(str, Enum):
    PDF = "pdf"
    DOCX = "docx"
    TXT = "txt"
    MARKDOWN = "markdown"
    IMAGE = "image"
    
class DocumentOptions(BaseModel):
    include_in_RAG: Optional[bool] = Field(True, alias="includeInRAG")
    is_job_document: Optional[bool] = Field(False, alias="isJobDocument")
    overwrite: Optional[bool] = Field(False, alias="overwrite")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }    

class Document(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    owner_id: str = Field(..., alias="ownerId")
    filename: str
    originalName: str
    type: DocumentType
    size: int
    upload_date: datetime = Field(default_factory=lambda: datetime.now(UTC), alias="uploadDate")
    options: DocumentOptions = Field(default_factory=DocumentOptions, alias="options")
    rag_chunks: Optional[int] = Field(default=0, alias="ragChunks")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }    

class DocumentContentResponse(BaseModel):
    document_id: str = Field(..., alias="documentId")
    filename: str
    type: DocumentType
    content: str
    size: int
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }    

class DocumentListResponse(BaseModel):
    documents: List[Document]
    total: int
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }    

class DocumentUpdateRequest(BaseModel):
    filename: Optional[str] = None
    options: Optional[DocumentOptions] = None
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }    

class Candidate(BaseUser):
    user_type: Literal[UserType.CANDIDATE] = Field(UserType.CANDIDATE, alias="userType")
    username: str
    description: Optional[str] = None
    resume: Optional[str] = None
    skills: Optional[List[Skill]] = None
    experience: Optional[List[WorkExperience]]  = None
    questions: Optional[List[CandidateQuestion]]  = None
    education: Optional[List[Education]]  = None
    preferred_job_types: Optional[List[EmploymentType]] = Field(None, alias="preferredJobTypes")
    desired_salary: Optional[DesiredSalary] = Field(None, alias="desiredSalary")
    availability_date: Optional[datetime] = Field(None, alias="availabilityDate")
    summary: Optional[str] = None
    languages: Optional[List[Language]]  = None
    certifications: Optional[List[Certification]] = None
    job_applications: Optional[List["JobApplication"]] = Field(None, alias="jobApplications")
    rags: List[RagEntry] = Field(default_factory=list)
    rag_content_size : int = 0

class CandidateAI(Candidate):
    user_type: Literal[UserType.CANDIDATE] = Field(UserType.CANDIDATE, alias="userType")
    is_AI: bool = Field(True, alias="isAI")
    age: Optional[int] = None
    gender: Optional[UserGender] = None
    ethnicity: Optional[str] = None

class Employer(BaseUser):
    user_type: Literal[UserType.EMPLOYER] = Field(UserType.EMPLOYER, alias="userType")
    company_name: str = Field(..., alias="companyName")
    industry: str
    description: Optional[str] = None
    company_size: str = Field(..., alias="companySize")
    company_description: str = Field(..., alias="companyDescription")
    website_url: Optional[HttpUrl] = Field(None, alias="websiteUrl")
    jobs: Optional[List["Job"]] = None
    company_logo: Optional[str] = Field(None, alias="companyLogo")
    social_links: Optional[List[SocialLink]] = Field(None, alias="socialLinks")
    poc: Optional[PointOfContact] = None

class Guest(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    session_id: str = Field(..., alias="sessionId")
    created_at: datetime = Field(..., alias="createdAt")
    last_activity: datetime = Field(..., alias="lastActivity")
    converted_to_user_id: Optional[str] = Field(None, alias="convertedToUserId")
    ip_address: Optional[str] = Field(None, alias="ipAddress")
    user_agent: Optional[str] = Field(None, alias="userAgent")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class Authentication(BaseModel):
    user_id: str = Field(..., alias="userId")
    password_hash: str = Field(..., alias="passwordHash")
    salt: str
    refresh_tokens: List[RefreshToken] = Field(..., alias="refreshTokens")
    reset_password_token: Optional[str] = Field(None, alias="resetPasswordToken")
    reset_password_expiry: Optional[datetime] = Field(None, alias="resetPasswordExpiry")
    last_password_change: datetime = Field(..., alias="lastPasswordChange")
    mfa_enabled: bool = Field(..., alias="mfaEnabled")
    mfa_method: Optional[MFAMethod] = Field(None, alias="mfaMethod")
    mfa_secret: Optional[str] = Field(None, alias="mfaSecret")
    login_attempts: int = Field(..., alias="loginAttempts")
    locked_until: Optional[datetime] = Field(None, alias="lockedUntil")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class AuthResponse(BaseModel):
    access_token: str = Field(..., alias="accessToken")
    refresh_token: str = Field(..., alias="refreshToken")
    user: Candidate | Employer
    expires_at: int = Field(..., alias="expiresAt")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class JobRequirements(BaseModel):
    technical_skills: Requirements = Field(..., alias="technicalSkills")
    experience_requirements: Requirements = Field(..., alias="experienceRequirements")
    soft_skills: Optional[List[str]] = Field(default_factory=list, alias="softSkills")
    experience: Optional[List[str]] = []
    education: Optional[List[str]] = []
    certifications: Optional[List[str]] = []
    preferred_attributes: Optional[List[str]] = Field(None, alias="preferredAttributes")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class Job(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    owner_id: str = Field(..., alias="ownerId")
    owner_type: UserType = Field(..., alias="ownerType")
    title: Optional[str]
    summary: Optional[str]
    company: Optional[str]
    description: str
    requirements: Optional[JobRequirements]
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class JobFull(Job):
    location: Location
    salary_range: Optional[SalaryRange] = Field(None, alias="salaryRange")
    employment_type: EmploymentType = Field(..., alias="employmentType")
    date_posted: datetime = Field(..., alias="datePosted")
    application_deadline: Optional[datetime] = Field(None, alias="applicationDeadline")
    is_active: bool = Field(..., alias="isActive")
    applicants: Optional[List["JobApplication"]] = None
    department: Optional[str] = None
    reports_to: Optional[str] = Field(None, alias="reportsTo")
    benefits: Optional[List[str]] = None
    visa_sponsorship: Optional[bool] = Field(None, alias="visaSponsorship")
    featured_until: Optional[datetime] = Field(None, alias="featuredUntil")
    views: int = 0
    application_count: int = Field(0, alias="applicationCount")

class InterviewFeedback(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    interview_id: str = Field(..., alias="interviewId")
    reviewer_id: str = Field(..., alias="reviewerId")
    technical_score: Annotated[float, Field(ge=0, le=10)] = Field(..., alias="technicalScore")
    cultural_score: Annotated[float, Field(ge=0, le=10)] = Field(..., alias="culturalScore")
    overall_score: Annotated[float, Field(ge=0, le=10)] = Field(..., alias="overallScore")
    strengths: List[str]
    weaknesses: List[str]
    recommendation: InterviewRecommendation
    comments: str
    created_at: datetime = Field(..., alias="createdAt")
    updated_at: datetime = Field(..., alias="updatedAt")
    is_visible: bool = Field(..., alias="isVisible")
    skill_assessments: Optional[List[SkillAssessment]] = Field(None, alias="skillAssessments")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class InterviewSchedule(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    application_id: str = Field(..., alias="applicationId")
    scheduled_date: datetime = Field(..., alias="scheduledDate")
    end_date: datetime = Field(..., alias="endDate")
    interview_type: InterviewType = Field(..., alias="interviewType")
    interviewers: List[str]
    location: Optional[Union[str, Location]] = None
    notes: Optional[str] = None
    feedback: Optional[InterviewFeedback] = None
    status: Literal["scheduled", "completed", "cancelled", "rescheduled"]
    meeting_link: Optional[HttpUrl] = Field(None, alias="meetingLink")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class JobApplication(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    job_id: str = Field(..., alias="jobId")
    candidate_id: str = Field(..., alias="candidateId")
    status: ApplicationStatus
    applied_date: datetime = Field(..., alias="appliedDate")
    updated_date: datetime = Field(..., alias="updatedDate")
    resume_version: str = Field(..., alias="resumeVersion")
    cover_letter: Optional[str] = Field(None, alias="coverLetter")
    notes: Optional[str] = None
    interview_schedules: Optional[List[InterviewSchedule]] = Field(None, alias="interviewSchedules")
    custom_questions: Optional[List[CustomQuestion]] = Field(None, alias="customQuestions")
    candidate_contact: Optional[CandidateContact] = Field(None, alias="candidateContact")
    decision: Optional[ApplicationDecision] = None
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class ChromaDBGetResponse(BaseModel):
    # Chroma fields
    ids: List[str] = []
    embeddings: List[List[float]] = []
    documents: List[str] = []
    metadatas: List[Dict[str, Any]] = []
    distances: List[float] = []
    # Additional fields
    name: str = ""
    size: int = 0
    dimensions: int = 2 | 3
    query: str = ""
    query_embedding: Optional[List[float]] = Field(default=None, alias="queryEmbedding")
    umap_embedding_2d: Optional[List[float]] = Field(default=None, alias="umapEmbedding2D")
    umap_embedding_3d: Optional[List[float]] = Field(default=None, alias="umapEmbedding3D")

class ChatContext(BaseModel):
    type: ChatContextType
    related_entity_id: Optional[str] = Field(None, alias="relatedEntityId")
    related_entity_type: Optional[Literal["job", "candidate", "employer"]] = Field(None, alias="relatedEntityType")
    additional_context: Optional[Dict[str, Any]] = Field({}, alias="additionalContext")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class ChatOptions(BaseModel):
    seed: Optional[int] = 8911
    num_ctx: Optional[int] = Field(default=None, alias="numCtx")  # Number of context tokens
    temperature: Optional[float] = Field(default=0.7) # Higher temperature to encourage tool usage
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

from llm_proxy import (LLMMessage)

class ApiMessage(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    session_id: str = Field(..., alias="sessionId")
    sender_id: Optional[str] = Field(None, alias="senderId")
    status: ApiStatusType
    type: ApiMessageType
    timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC), alias="timestamp")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

MOCK_UUID = str(uuid.uuid4())

class ChatMessageStreaming(ApiMessage):
    status: ApiStatusType = ApiStatusType.STREAMING
    type: ApiMessageType = ApiMessageType.TEXT
    content: str

class ApiActivityType(str, Enum):
    SYSTEM = "system"         # Used solely on frontend
    INFO = "info"             # Used solely on frontend
    SEARCHING = "searching"   # Used when generating RAG information
    THINKING = "thinking"     # Used when determing if AI will use tools
    GENERATING = "generating" # Used when AI is generating a response
    CONVERTING = "converting" # Used when AI is generating a response
    GENERATING_IMAGE = "generating_image" # Used when AI is generating an image
    TOOLING = "tooling"       # Used when AI is using tools
    HEARTBEAT = "heartbeat"   # Used for periodic updates

class ChatMessageStatus(ApiMessage):
    status: ApiStatusType = ApiStatusType.STATUS
    type: ApiMessageType = ApiMessageType.TEXT
    activity: ApiActivityType
    content: Any

class ChatMessageError(ApiMessage):
    status: ApiStatusType = ApiStatusType.ERROR
    type: ApiMessageType = ApiMessageType.TEXT
    content: str

class ChatMessageRagSearch(ApiMessage):
    type: ApiMessageType = ApiMessageType.JSON
    dimensions: int = 2 | 3
    content: List[ChromaDBGetResponse] = []

class JobRequirementsMessage(ApiMessage):
    type: ApiMessageType = ApiMessageType.JSON
    title: Optional[str]
    summary: Optional[str]
    company: Optional[str]
    description: str
    requirements: Optional[JobRequirements]

class DocumentMessage(ApiMessage):
    type: ApiMessageType = ApiMessageType.JSON
    document: Document = Field(..., alias="document")
    content: Optional[str] = ""
    converted: bool = Field(False, alias="converted")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class ChatMessageMetaData(BaseModel):
    model: AIModelType = AIModelType.QWEN2_5
    temperature: float = 0.7
    max_tokens: int = Field(default=8092, alias="maxTokens")
    top_p: float = Field(default=1, alias="topP")
    frequency_penalty: Optional[float] = Field(None, alias="frequencyPenalty")
    presence_penalty: Optional[float] = Field(None, alias="presencePenalty")
    stop_sequences: Optional[List[str]] = Field(None, alias="stopSequences")
    rag_results: List[ChromaDBGetResponse] = Field(default_factory=list, alias="ragResults")
    llm_history: List[LLMMessage] = Field(default_factory=list, alias="llmHistory")
    eval_count: int = 0
    eval_duration: int = 0
    prompt_eval_count: int = 0
    prompt_eval_duration: int = 0
    options: Optional[ChatOptions] = None
    tools: Dict[str, Any] = Field(default_factory=dict)
    timers: Dict[str, float] = Field(default_factory=dict)
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class ChatMessageUser(ApiMessage):
    type: ApiMessageType = ApiMessageType.TEXT
    status: ApiStatusType = ApiStatusType.DONE
    role: ChatSenderType = ChatSenderType.USER
    content: str = ""
    tunables: Optional[Tunables] = None

class ChatMessage(ChatMessageUser):
    role: ChatSenderType = ChatSenderType.ASSISTANT
    metadata: ChatMessageMetaData = Field(default_factory=ChatMessageMetaData)
    #attachments: Optional[List[Attachment]] = None
    #reactions: Optional[List[MessageReaction]] = None
    #is_edited: bool = Field(False, alias="isEdited")
    #edit_history: Optional[List[EditHistory]] = Field(None, alias="editHistory")

class ChatSession(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    user_id: Optional[str] = Field(None, alias="userId")
    guest_id: Optional[str] = Field(None, alias="guestId")
    created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias="createdAt")
    last_activity: datetime = Field(default_factory=lambda: datetime.now(UTC), alias="lastActivity")
    title: Optional[str] = None
    context: ChatContext
    # messages: Optional[List[ChatMessage]] = None
    is_archived: bool = Field(False, alias="isArchived")
    system_prompt: Optional[str] = Field(None, alias="systemPrompt")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

    @model_validator(mode="after")
    def check_user_or_guest(self) -> "ChatSession":
        if not self.user_id and not self.guest_id:
            raise ValueError("Either user_id or guest_id must be provided")
        return self

class DataSourceConfiguration(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    rag_config_id: str = Field(..., alias="ragConfigId")
    name: str
    source_type: DataSourceType = Field(..., alias="sourceType")
    connection_details: Dict[str, Any] = Field(..., alias="connectionDetails")
    processing_pipeline: List[ProcessingStep] = Field(..., alias="processingPipeline")
    refresh_schedule: Optional[str] = Field(None, alias="refreshSchedule")
    last_refreshed: Optional[datetime] = Field(None, alias="lastRefreshed")
    status: Literal["active", "pending", "error", "processing"]
    error_details: Optional[str] = Field(None, alias="errorDetails")
    metadata: Optional[Dict[str, Any]] = None
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class RAGConfiguration(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    user_id: str = Field(..., alias="userId")
    name: str
    description: Optional[str] = None
    data_source_configurations: List[DataSourceConfiguration] = Field(..., alias="dataSourceConfigurations")
    embedding_model: str = Field(..., alias="embeddingModel")
    vector_store_type: VectorStoreType = Field(..., alias="vectorStoreType")
    retrieval_parameters: RetrievalParameters = Field(..., alias="retrievalParameters")
    created_at: datetime = Field(..., alias="createdAt")
    updated_at: datetime = Field(..., alias="updatedAt")
    version: int
    is_active: bool = Field(..., alias="isActive")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class UserActivity(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    user_id: Optional[str] = Field(None, alias="userId")
    guest_id: Optional[str] = Field(None, alias="guestId")
    activity_type: ActivityType = Field(..., alias="activityType")
    timestamp: datetime
    metadata: Dict[str, Any]
    ip_address: Optional[str] = Field(None, alias="ipAddress")
    user_agent: Optional[str] = Field(None, alias="userAgent")
    session_id: Optional[str] = Field(None, alias="sessionId")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

    @model_validator(mode="after")
    def check_user_or_guest(self) -> "ChatSession":
        if not self.user_id and not self.guest_id:
            raise ValueError("Either user_id or guest_id must be provided")
        return self

class Analytics(BaseModel):
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    entity_type: Literal["job", "candidate", "chat", "system", "employer"] = Field(..., alias="entityType")
    entity_id: str = Field(..., alias="entityId")
    metric_type: str = Field(..., alias="metricType")
    value: float
    timestamp: datetime
    dimensions: Optional[Dict[str, Any]] = None
    segment: Optional[str] = None
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class UserPreference(BaseModel):
    user_id: str = Field(..., alias="userId")
    theme: ThemePreference
    notifications: List[NotificationPreference]
    accessibility: AccessibilitySettings
    dashboard_layout: Optional[Dict[str, Any]] = Field(None, alias="dashboardLayout")
    language: str
    timezone: str
    email_frequency: Literal["immediate", "daily", "weekly", "never"] = Field(..., alias="emailFrequency")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

# ============================
# API Request/Response Models
# ============================
class CreateCandidateRequest(BaseModel):
    email: EmailStr
    username: str
    password: str
    first_name: str = Field(..., alias="firstName")
    last_name: str = Field(..., alias="lastName")
    # Add other required candidate fields as needed
    phone: Optional[str] = None
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }
    
    @field_validator('username')
    def validate_username(cls, v):
        if not v or len(v.strip()) < 3:
            raise ValueError('Username must be at least 3 characters long')
        return v.strip().lower()
    
    @field_validator('password')
    def validate_password_strength(cls, v):
        is_valid, issues = validate_password_strength(v)
        if not is_valid:
            raise ValueError('; '.join(issues))
        return v

# Create Employer Endpoint (similar pattern)
class CreateEmployerRequest(BaseModel):
    email: EmailStr
    username: str
    password: str
    company_name: str = Field(..., alias="companyName")
    industry: str
    company_size: str = Field(..., alias="companySize") 
    company_description: str = Field(..., alias="companyDescription")
    # Add other required employer fields
    website_url: Optional[str] = Field(None, alias="websiteUrl")
    phone: Optional[str] = None
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }
    
    @field_validator('username')
    def validate_username(cls, v):
        if not v or len(v.strip()) < 3:
            raise ValueError('Username must be at least 3 characters long')
        return v.strip().lower()
    
    @field_validator('password')
    def validate_password_strength(cls, v):
        is_valid, issues = validate_password_strength(v)
        if not is_valid:
            raise ValueError('; '.join(issues))
        return v

class ChatQuery(BaseModel):
    prompt: str
    tunables: Optional[Tunables] = None
    agent_options: Optional[Dict[str, Any]] = Field(None, alias="agentOptions")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class PaginatedRequest(BaseModel):
    page: Annotated[int, Field(ge=1)] = 1
    limit: Annotated[int, Field(ge=1, le=100)] = 20
    sort_by: Optional[str] = Field(None, alias="sortBy")
    sort_order: Optional[SortOrder] = Field(None, alias="sortOrder")
    filters: Optional[Dict[str, Any]] = None
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class SearchQuery(BaseModel):
    query: str
    filters: Optional[Dict[str, Any]] = None
    page: Annotated[int, Field(ge=1)] = 1
    limit: Annotated[int, Field(ge=1, le=100)] = 20
    sort_by: Optional[str] = Field(None, alias="sortBy")
    sort_order: Optional[SortOrder] = Field(None, alias="sortOrder")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class PaginatedResponse(BaseModel):
    data: List[Any]  # Will be typed specifically when used
    total: int
    page: int
    limit: int
    total_pages: int = Field(..., alias="totalPages")
    has_more: bool = Field(..., alias="hasMore")
    model_config = {
        "populate_by_name": True   # Allow both field names and aliases        
    }

class ApiResponse(BaseModel):
    success: bool
    data: Optional[Any] = None  # Will be typed specifically when used
    error: Optional[ErrorDetail] = None
    meta: Optional[Dict[str, Any]] = None

# Specific typed response models for common use cases
class CandidateResponse(BaseModel):
    success: bool
    data: Optional[Candidate] = None
    error: Optional[ErrorDetail] = None
    meta: Optional[Dict[str, Any]] = None

class EmployerResponse(BaseModel):
    success: bool
    data: Optional[Employer] = None
    error: Optional[ErrorDetail] = None
    meta: Optional[Dict[str, Any]] = None

class JobResponse(BaseModel):
    success: bool
    data: Optional["Job"] = None
    error: Optional[ErrorDetail] = None
    meta: Optional[Dict[str, Any]] = None

class CandidateListResponse(BaseModel):
    success: bool
    data: Optional[List[Candidate]] = None
    error: Optional[ErrorDetail] = None
    meta: Optional[Dict[str, Any]] = None

class JobListResponse(BaseModel):
    success: bool
    data: Optional[List["Job"]] = None
    error: Optional[ErrorDetail] = None
    meta: Optional[Dict[str, Any]] = None

# Forward references resolution
Candidate.update_forward_refs()
Employer.update_forward_refs()
ChatSession.update_forward_refs()
JobApplication.update_forward_refs()
JobFull.update_forward_refs()