import os
import smtplib
from email.message import EmailMessage

from flask import current_app, request
from sqlalchemy import or_

from .extensions import db
from .models import (
    ActionItem,
    AuditLog,
    Branch,
    Department,
    Incident,
    Notification,
    Permission,
    Role,
    User,
    WorkflowLog,
)
from .policy import utcnow


PERMISSIONS = {
    "incident.create": "Create occurrence reports",
    "incident.view": "View occurrence reports within assigned scope",
    "incident.view_reporter_identity": "View protected reporter identity",
    "incident.manager_review": "Complete supervisor/manager review",
    "incident.qps_review": "Complete QPS review and analysis",
    "incident.rca": "Complete RCA investigation",
    "incident.action": "Manage assigned corrective/preventive actions",
    "incident.close": "Perform final QPS closure",
    "incident.export": "Export confidential OVR PDFs",
    "reports.view": "View analytics and quarterly reports",
    "admin.branches": "Manage branches and departments",
    "admin.users": "Manage users",
    "admin.roles": "Manage roles and permissions",
    "admin.audit": "View audit logs",
}

DEFAULT_ROLES = {
    "system_admin": {
        "name": "System Administrator", "scope": "all", "permissions": list(PERMISSIONS),
    },
    "qps": {
        "name": "Quality & Patient Safety", "scope": "all",
        "permissions": [
            "incident.create", "incident.view", "incident.view_reporter_identity", "incident.qps_review",
            "incident.rca", "incident.action", "incident.close", "incident.export", "reports.view",
        ],
    },
    "branch_director": {
        "name": "Branch Director", "scope": "branch",
        "permissions": [
            "incident.create", "incident.view", "incident.manager_review", "incident.rca",
            "incident.action", "incident.export", "reports.view",
        ],
    },
    "department_manager": {
        "name": "Department Manager", "scope": "department",
        "permissions": ["incident.create", "incident.view", "incident.manager_review", "incident.action", "incident.export"],
    },
    "rca_reviewer": {
        "name": "RCA Reviewer", "scope": "branch",
        "permissions": ["incident.create", "incident.view", "incident.rca", "incident.action", "incident.export"],
    },
    "reporter": {
        "name": "Reporter / Staff", "scope": "own",
        "permissions": ["incident.create", "incident.view", "incident.export"],
    },
}


def seed_reference_data(admin_username=None, admin_email=None, admin_password=None):
    permissions = {}
    for code, label in PERMISSIONS.items():
        permission = db.session.scalar(db.select(Permission).where(Permission.code == code))
        if not permission:
            permission = Permission(code=code, label=label)
            db.session.add(permission)
        permissions[code] = permission
    db.session.flush()

    roles = {}
    for code, definition in DEFAULT_ROLES.items():
        role = db.session.scalar(db.select(Role).where(Role.code == code))
        if not role:
            role = Role(code=code, name=definition["name"], scope=definition["scope"], is_system=True)
            db.session.add(role)
        role.name = definition["name"]
        role.scope = definition["scope"]
        role.permissions = [permissions[item] for item in definition["permissions"]]
        roles[code] = role
    db.session.flush()

    branch = db.session.scalar(db.select(Branch).where(Branch.code == "MAIN"))
    if not branch:
        branch = Branch(code="MAIN", name="Al Wattan Medical Group - Main Medical Complex", is_active=True)
        db.session.add(branch)
        db.session.flush()

    departments = {}
    for code, name in [("QPS", "Quality & Patient Safety"), ("ADM", "Administration"), ("MED", "Medical Services")]:
        department = db.session.scalar(
            db.select(Department).where(Department.branch_id == branch.id, Department.code == code)
        )
        if not department:
            department = Department(branch_id=branch.id, code=code, name=name, is_active=True)
            db.session.add(department)
        departments[code] = department
    db.session.flush()

    admin = None
    if admin_username and admin_password:
        admin = db.session.scalar(db.select(User).where(User.username == admin_username.lower().strip()))
        if not admin:
            admin = User(
                username=admin_username.lower().strip(),
                email=(admin_email or None),
                full_name="System Administrator",
                employee_id="ADMIN-001",
                position="System Administrator",
                role=roles["system_admin"],
                branch=branch,
                department=departments["ADM"],
                is_active=True,
                must_change_password=True,
            )
            admin.set_password(admin_password)
            db.session.add(admin)
    db.session.commit()
    return admin


def audit(action, entity_type, entity_id=None, details=None, actor=None):
    from flask_login import current_user

    if actor is None and getattr(current_user, "is_authenticated", False):
        actor = current_user
    log = AuditLog(
        actor_id=getattr(actor, "id", None),
        action=action,
        entity_type=entity_type,
        entity_id=str(entity_id) if entity_id is not None else None,
        details=details,
        ip_address=(request.headers.get("X-Forwarded-For", request.remote_addr or "")[:64] if request else None),
    )
    db.session.add(log)


def send_optional_email(user, title, message):
    if not current_app.config.get("MAIL_ENABLED") or not user or not user.email:
        return
    mail = EmailMessage()
    mail["Subject"] = title
    mail["From"] = current_app.config.get("MAIL_FROM")
    mail["To"] = user.email
    mail.set_content(message)
    try:
        with smtplib.SMTP(current_app.config["MAIL_HOST"], current_app.config["MAIL_PORT"], timeout=8) as server:
            if current_app.config.get("MAIL_USE_TLS"):
                server.starttls()
            if current_app.config.get("MAIL_USERNAME"):
                server.login(current_app.config["MAIL_USERNAME"], current_app.config.get("MAIL_PASSWORD", ""))
            server.send_message(mail)
    except Exception as exc:  # Never break clinical reporting because SMTP is unavailable.
        current_app.logger.warning("Email notification failed: %s", exc)


def notify(user, title, message, incident=None):
    if not user or not user.is_active:
        return
    db.session.add(Notification(user_id=user.id, title=title, message=message, incident_id=getattr(incident, "id", None)))
    send_optional_email(user, title, message)


def notify_role(permission_code, title, message, incident=None, branch_id=None):
    query = db.select(User).join(User.role).join(Role.permissions).where(
        Permission.code == permission_code, User.is_active.is_(True)
    )
    if branch_id:
        query = query.where(or_(Role.scope == "all", User.branch_id == branch_id))
    for user in db.session.scalars(query).unique().all():
        notify(user, title, message, incident)


def assign_reference(incident):
    db.session.flush()
    if not incident.reference_no:
        incident.reference_no = f"AWMG-OVR-{utcnow():%Y}-{incident.id:06d}"


def transition(incident, to_status, action, notes=None, actor=None):
    from flask_login import current_user

    actor = actor or current_user
    previous = incident.status
    incident.status = to_status
    db.session.add(
        WorkflowLog(
            incident=incident,
            actor_id=getattr(actor, "id", None),
            from_status=previous,
            to_status=to_status,
            action=action,
            notes=notes,
        )
    )
    audit("WORKFLOW_TRANSITION", "Incident", incident.id, f"{previous} -> {to_status}: {action}", actor=actor)


def incident_query_for(user):
    query = db.select(Incident)
    if not user or not user.is_authenticated or not user.can("incident.view"):
        return query.where(db.false())
    scope = user.role.scope
    if scope == "all":
        return query
    if scope == "branch":
        return query.where(Incident.branch_id == user.branch_id)
    if scope == "department":
        return query.where(
            or_(
                Incident.department_id == user.department_id,
                Incident.reporter_id == user.id,
                Incident.assigned_manager_id == user.id,
                Incident.action_items.any(ActionItem.owner_id == user.id),
            )
        )
    return query.where(Incident.reporter_id == user.id)


def can_view_incident(user, incident):
    if not user or not user.is_authenticated or not user.can("incident.view"):
        return False
    if user.role.scope == "all":
        return True
    if user.role.scope == "branch" and incident.branch_id == user.branch_id:
        return True
    if user.role.scope == "department" and (
        incident.department_id == user.department_id
        or incident.reporter_id == user.id
        or incident.assigned_manager_id == user.id
        or any(item.owner_id == user.id for item in incident.action_items)
    ):
        return True
    return incident.reporter_id == user.id


def reporter_display(user, incident):
    if incident.is_anonymous and not user.can("incident.view_reporter_identity"):
        return "Anonymous reporter"
    return incident.reporter.full_name


def find_manager(incident):
    candidates = db.session.scalars(
        db.select(User)
        .join(User.role)
        .join(Role.permissions)
        .where(
            User.is_active.is_(True),
            Permission.code == "incident.manager_review",
            User.branch_id == incident.branch_id,
            or_(User.department_id == incident.department_id, Role.scope == "branch"),
        )
        .order_by(db.case((User.department_id == incident.department_id, 0), else_=1), User.id)
    ).unique().all()
    return candidates[0] if candidates else None


def find_qps(incident):
    candidates = db.session.scalars(
        db.select(User)
        .join(User.role)
        .join(Role.permissions)
        .where(User.is_active.is_(True), Permission.code == "incident.qps_review")
        .order_by(db.case((User.branch_id == incident.branch_id, 0), else_=1), User.id)
    ).unique().all()
    return candidates[0] if candidates else None


def upload_root():
    path = current_app.config["UPLOAD_FOLDER"]
    os.makedirs(path, exist_ok=True)
    return path
