import time
import random
import sys
from typing import TypedDict
from langgraph.graph import StateGraph, END


class AgentState(TypedDict):
    """This dictionary is passed along the graph. Each node can read or update it."""
    user_input: str
    intent: str
    agent_response: str
    final_output: str

 
class AdmissionAgent:
    def get_info(self) -> str:
        return (
            " [ADMISSION DESK]\n"
            "University Admissions Portal.\n\n"
            "Here is the UGC-approved admission process for the 2026 academic session:\n"
            "  1. Application: Register via the official portal (requires Aadhar integration).\n"
            "  2. Eligibility: Valid JEE Main or CUET UG score is mandatory for B.Tech.\n"
            "  3. Documentation: Upload 10th & 12th Marksheets, Transfer Certificate (TC),\n"
            "     and Category Certificate (if applicable).\n"
            "  4. Fee Payment: Pay the ₹2,000 non-refundable counseling fee via UPI or NetBanking.\n\n"
            " Pro-Tip: Seat allocation is based on centralized counseling rounds (JoSAA/CSAB). "
            "Keep an eye on the portal for cutoff dates!"
        )

class ExamAgent:
    def get_info(self) -> str:
        return (
            " [EXAMINATION CELL]\n"
            "Important Updates for the Current Odd Semester (Winter):\n\n"
            "  • End-Semester Exams: Commencing strictly from 10th December.\n"
            "  • Hall Tickets: Will be available on the college portal 5 days prior.\n"
            "  • Dues Clearance: Ensure all library and hostel dues are cleared to download your Admit Card.\n\n"
            " Note: A minimum of 75% attendance is strictly required to appear for examinations as per university norms."
        )

class FeesAgent:
    def get_info(self) -> str:
        return (
            " [ACCOUNTS & BILLING]\n"
            "Fee Structure for First-Year B.Tech (Per Semester):\n\n"
            "  • Tuition Fee         : ₹65,000\n"
            "  • Development Fund    : ₹12,000\n"
            "  • Hostel & Mess (Avg) : ₹35,000\n"
            "  • Caution Money       : ₹5,000 (Refundable)\n"
            "  ----------------------------------\n"
            "  Total Estimated       : ₹1,17,000\n\n"
        )

class ScholarshipAgent:
    def get_info(self) -> str:
        return (
            " [SCHOLARSHIP & WELFARE]\n"
            "We believe financial constraints should not hinder education. Available schemes:\n\n"
            "  1. Merit-cum-Means (MCM): 50% tuition waiver for students with family income < ₹5 LPA and CGPA > 8.0.\n"
            "  2. State/Central Schemes: Apply directly via the National Scholarship Portal (NSP).\n"
            "  3. Jharkhand eKalyan: Post-matric scholarship for eligible SC/ST/BC students domiciled in Jharkhand.\n"
            "  4. PMSSS: Special scheme for students from J&K and Ladakh.\n"
            "  5. Category Waivers: As per Govt. of India reservation guidelines for SC/ST/OBC (NCL).\n\n"
            "Visit the Dean of Student Welfare (DSW) office for manual form verifications."
        )

class SemanticRouter:
    def __init__(self):
        self.intent_map = {
            "admission": ["admission", "apply", "join", "enroll", "cutoff", "jee", "cuet", "process"],
            "exam": ["exam", "test", "admit card", "hall ticket", "date sheet", "marks", "result", "semester"],
            "fees": ["fee", "cost", "money", "pay", "tuition", "hostel fee", "loan", "dues"],
            "scholarship": ["scholarship", "waiver", "concession", "financial aid", "nsp", "mcm"],
            "help": ["help", "options", "menu", "what can you do"],
            "exit": ["bye", "exit", "quit", "leave", "close"]
        }

    def detect_intent(self, text: str) -> str:
        text = text.lower().strip()
        for intent, keywords in self.intent_map.items():
            if any(keyword in text for keyword in keywords):
                return intent
        return "unknown"


def node_intent_classifier(state: AgentState):
    """Classifies the user input and updates the state."""
    router = SemanticRouter()
    intent = router.detect_intent(state["user_input"])
    return {"intent": intent}

def node_admission_agent(state: AgentState):
    return {"agent_response": AdmissionAgent().get_info()}

def node_exam_agent(state: AgentState):
    return {"agent_response": ExamAgent().get_info()}

def node_fees_agent(state: AgentState):
    return {"agent_response": FeesAgent().get_info()}

def node_scholarship_agent(state: AgentState):
    return {"agent_response": ScholarshipAgent().get_info()}

def node_response_agent(state: AgentState):
    """Formats the final response based on what was gathered or if it was a system command."""
    intent = state.get("intent")
    
    if intent == "exit":
        messages = [
            "Session terminated. Best of luck with your academic journey!",
            "Goodbye! Feel free to return if you have more questions. ",
            "Logging off."
        ]
        final_text = random.choice(messages)
    elif intent == "help":
        final_text = (
            " [SYSTEM HELPER]\n"
            "I am the AI Assistant for the University.\n"
            "Try asking me things like:\n"
            "  • How do I apply for admission?\n"
            "  • When are the semester exams?\n"
            "  • What is the B.Tech fee structure?\n"
            "  • Are there any scholarships available?\n\n"
            "Type 'exit' to close the session."
        )
    elif intent == "unknown":
        final_text = (
            " [SYSTEM] I couldn't quite understand that query.\n"
            "Currently, I am programmed to assist with Admissions, Exams, Fees, and Scholarships.\n"
            "Please try rephrasing or type 'help' to see your options."
        )
    else:
        
        final_text = state.get("agent_response", "Error generating response.")

    return {"final_output": final_text}

def route_by_intent(state: AgentState) -> str:
    """Reads the intent from the state and determines the next node."""
    intent = state["intent"]
    valid_agents = ["admission", "exam", "fees", "scholarship"]
    
    if intent in valid_agents:
        return f"{intent}_agent" 
    else:
        return "response_agent" 


workflow = StateGraph(AgentState)

workflow.add_node("intent_classifier", node_intent_classifier)
workflow.add_node("admission_agent", node_admission_agent)
workflow.add_node("exam_agent", node_exam_agent)
workflow.add_node("fees_agent", node_fees_agent)
workflow.add_node("scholarship_agent", node_scholarship_agent)
workflow.add_node("response_agent", node_response_agent)

workflow.set_entry_point("intent_classifier")

workflow.add_conditional_edges(
    "intent_classifier",
    route_by_intent,
    {
        "admission_agent": "admission_agent",
        "exam_agent": "exam_agent",
        "fees_agent": "fees_agent",
        "scholarship_agent": "scholarship_agent",
        "response_agent": "response_agent" 
    }
)

workflow.add_edge("admission_agent", "response_agent")
workflow.add_edge("exam_agent", "response_agent")
workflow.add_edge("fees_agent", "response_agent")
workflow.add_edge("scholarship_agent", "response_agent")
workflow.add_edge("response_agent", END)

app = workflow.compile()

def print_delayed(text: str, delay: float = 0.015):
    """Creates a professional typing effect in the terminal."""
    for char in text:
        sys.stdout.write(char)
        sys.stdout.flush()
        time.sleep(delay)
    print("\n")

def main():
    print("=" * 60)
    print("  LANGGRAPH BASED UNIVERSITY AI SYSTEM")
    print("=" * 60)
    
    user_name = input("\n Enter your name to initiate session: ").strip().title()
    if not user_name:
        user_name = "Student"

    welcome_msg = (
        f"\nWelcome, {user_name}! I am the centralized AI Assistant.\n"
        "How can I help you today? (Type 'help' for options)"
    )
    print_delayed(welcome_msg)

    while True:
        user_input = input(f"\n{user_name} > ")
        if not user_input.strip():
            continue

        initial_state = {"user_input": user_input, "intent": "", "agent_response": "", "final_output": ""}
        result_state = app.invoke(initial_state)

        time.sleep(0.3)
        print("-" * 60)
        
        print_delayed(result_state["final_output"])
        print("-" * 60)

        if result_state["intent"] == "exit":
            break

if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\n Exiting system...")
        sys.exit(0)