import json
from flask_login import UserMixin
from werkzeug.security import check_password_hash, generate_password_hash

from .extensions import db, login_manager
from .policy import STATUS_LABELS, utcnow


role_permissions = db.Table(
    "role_permissions",
    db.Column("role_id", db.Integer, db.ForeignKey("roles.id", ondelete="CASCADE"), primary_key=True),
    db.Column("permission_id", db.Integer, db.ForeignKey("permissions.id", ondelete="CASCADE"), primary_key=True),
)


incident_departments = db.Table(
    "incident_departments",
    db.Column("incident_id", db.Integer, db.ForeignKey("incidents.id", ondelete="CASCADE"), primary_key=True),
    db.Column("department_id", db.Integer, db.ForeignKey("departments.id", ondelete="CASCADE"), primary_key=True),
)


class TimestampMixin:
    created_at = db.Column(db.DateTime, default=utcnow, nullable=False)
    updated_at = db.Column(db.DateTime, default=utcnow, onupdate=utcnow, nullable=False)


class Branch(TimestampMixin, db.Model):
    __tablename__ = "branches"
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String(20), unique=True, nullable=False)
    name = db.Column(db.String(160), nullable=False)
    address = db.Column(db.String(500))
    is_active = db.Column(db.Boolean, default=True, nullable=False)
    departments = db.relationship("Department", back_populates="branch", cascade="all, delete-orphan")


class Department(TimestampMixin, db.Model):
    __tablename__ = "departments"
    __table_args__ = (db.UniqueConstraint("branch_id", "name", name="uq_department_branch_name"),)
    id = db.Column(db.Integer, primary_key=True)
    branch_id = db.Column(db.Integer, db.ForeignKey("branches.id", ondelete="CASCADE"), nullable=False, index=True)
    code = db.Column(db.String(30), nullable=False)
    name = db.Column(db.String(160), nullable=False)
    is_active = db.Column(db.Boolean, default=True, nullable=False)
    branch = db.relationship("Branch", back_populates="departments")


class Permission(db.Model):
    __tablename__ = "permissions"
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String(80), unique=True, nullable=False)
    label = db.Column(db.String(160), nullable=False)


class Role(TimestampMixin, db.Model):
    __tablename__ = "roles"
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String(50), unique=True, nullable=False)
    name = db.Column(db.String(120), nullable=False)
    scope = db.Column(db.String(20), default="own", nullable=False)  # own, department, branch, all
    is_system = db.Column(db.Boolean, default=False, nullable=False)
    permissions = db.relationship("Permission", secondary=role_permissions, lazy="selectin")

    def allows(self, code):
        return any(permission.code == code for permission in self.permissions)


class User(UserMixin, TimestampMixin, db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False, index=True)
    email = db.Column(db.String(255), unique=True, nullable=True, index=True)
    full_name = db.Column(db.String(180), nullable=False)
    employee_id = db.Column(db.String(80), unique=True, nullable=True)
    mobile = db.Column(db.String(50))
    position = db.Column(db.String(120))
    password_hash = db.Column(db.String(255), nullable=False)
    role_id = db.Column(db.Integer, db.ForeignKey("roles.id"), nullable=False)
    branch_id = db.Column(db.Integer, db.ForeignKey("branches.id"), nullable=True)
    department_id = db.Column(db.Integer, db.ForeignKey("departments.id"), nullable=True)
    is_active = db.Column(db.Boolean, default=True, nullable=False)
    must_change_password = db.Column(db.Boolean, default=True, nullable=False)
    last_login_at = db.Column(db.DateTime)
    role = db.relationship("Role", lazy="joined")
    branch = db.relationship("Branch", lazy="joined")
    department = db.relationship("Department", lazy="joined")

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

    def can(self, permission):
        return bool(self.is_active and self.role and self.role.allows(permission))


@login_manager.user_loader
def load_user(user_id):
    return db.session.get(User, int(user_id))


class Incident(TimestampMixin, db.Model):
    __tablename__ = "incidents"
    id = db.Column(db.Integer, primary_key=True)
    reference_no = db.Column(db.String(40), unique=True, index=True)
    status = db.Column(db.String(30), default="DRAFT", nullable=False, index=True)
    branch_id = db.Column(db.Integer, db.ForeignKey("branches.id"), nullable=False, index=True)
    department_id = db.Column(db.Integer, db.ForeignKey("departments.id"), nullable=False, index=True)
    reporter_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False, index=True)
    assigned_manager_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True, index=True)
    assigned_qps_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True, index=True)

    is_anonymous = db.Column(db.Boolean, default=False, nullable=False)
    incident_date = db.Column(db.Date, nullable=False)
    incident_time = db.Column(db.Time, nullable=False)
    location = db.Column(db.String(255), nullable=False)
    person_type = db.Column(db.String(40), nullable=False)
    person_name = db.Column(db.String(180))
    person_position = db.Column(db.String(120))
    person_mobile_badge = db.Column(db.String(100))
    person_department = db.Column(db.String(160))
    patient_mrn = db.Column(db.String(100))

    classification_category = db.Column(db.String(100), nullable=False)
    classification_items_json = db.Column(db.Text, default="[]", nullable=False)
    classification_other = db.Column(db.String(255))
    report_type = db.Column(db.String(60), nullable=False)
    injury_occurred = db.Column(db.Boolean, default=False, nullable=False)
    injury_type = db.Column(db.String(40))
    harm_level = db.Column(db.String(40))
    likelihood = db.Column(db.String(40))
    medication_harm_category = db.Column(db.String(1))
    description = db.Column(db.Text, nullable=False)
    immediate_action = db.Column(db.Text)

    supervisor_action = db.Column(db.Text)
    patient_involved_confirmed = db.Column(db.Boolean)
    medical_record_documented = db.Column(db.Boolean)
    sentinel_confirmed = db.Column(db.Boolean, default=False, nullable=False)
    sentinel_event_type = db.Column(db.String(255))
    contributing_factors_json = db.Column(db.Text, default="[]", nullable=False)
    incident_causes = db.Column(db.Text)
    consequence = db.Column(db.String(40))
    risk_score = db.Column(db.Integer)
    risk_level = db.Column(db.String(20), index=True)
    risk_registered = db.Column(db.Boolean, default=False, nullable=False)

    qps_analysis = db.Column(db.Text)
    qps_recommendation = db.Column(db.Text)
    final_feedback = db.Column(db.Text)
    closure_reason = db.Column(db.Text)
    red_closure_approval_recorded = db.Column(db.Boolean, default=False, nullable=False)
    red_closure_approved_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
    feedback_acknowledged_at = db.Column(db.DateTime)

    submitted_at = db.Column(db.DateTime)
    manager_reviewed_at = db.Column(db.DateTime)
    qps_reviewed_at = db.Column(db.DateTime)
    investigation_due_at = db.Column(db.DateTime)
    action_plan_due_at = db.Column(db.DateTime)
    qps_closure_due_at = db.Column(db.DateTime)
    closed_at = db.Column(db.DateTime)

    branch = db.relationship("Branch", lazy="joined")
    department = db.relationship("Department", foreign_keys=[department_id], lazy="joined")
    reporter = db.relationship("User", foreign_keys=[reporter_id], lazy="joined")
    assigned_manager = db.relationship("User", foreign_keys=[assigned_manager_id], lazy="joined")
    assigned_qps = db.relationship("User", foreign_keys=[assigned_qps_id], lazy="joined")
    red_closure_approved_by = db.relationship("User", foreign_keys=[red_closure_approved_by_id], lazy="joined")
    involved_departments = db.relationship("Department", secondary=incident_departments, lazy="selectin")
    workflow_logs = db.relationship("WorkflowLog", back_populates="incident", cascade="all, delete-orphan", order_by="WorkflowLog.created_at")
    action_items = db.relationship("ActionItem", back_populates="incident", cascade="all, delete-orphan", order_by="ActionItem.created_at")
    attachments = db.relationship("Attachment", back_populates="incident", cascade="all, delete-orphan", order_by="Attachment.created_at")
    comments = db.relationship("Comment", back_populates="incident", cascade="all, delete-orphan", order_by="Comment.created_at")

    @property
    def classification_items(self):
        try:
            return json.loads(self.classification_items_json or "[]")
        except (TypeError, ValueError):
            return []

    @classification_items.setter
    def classification_items(self, value):
        self.classification_items_json = json.dumps(value or [])

    @property
    def contributing_factors(self):
        try:
            return json.loads(self.contributing_factors_json or "[]")
        except (TypeError, ValueError):
            return []

    @contributing_factors.setter
    def contributing_factors(self, value):
        self.contributing_factors_json = json.dumps(value or [])

    @property
    def status_label(self):
        return STATUS_LABELS.get(self.status, self.status.replace("_", " ").title())

    @property
    def occurred_at_display(self):
        return f"{self.incident_date:%Y-%m-%d} {self.incident_time:%H:%M}"


class WorkflowLog(db.Model):
    __tablename__ = "workflow_logs"
    id = db.Column(db.Integer, primary_key=True)
    incident_id = db.Column(db.Integer, db.ForeignKey("incidents.id", ondelete="CASCADE"), nullable=False, index=True)
    actor_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
    from_status = db.Column(db.String(30))
    to_status = db.Column(db.String(30), nullable=False)
    action = db.Column(db.String(160), nullable=False)
    notes = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=utcnow, nullable=False)
    incident = db.relationship("Incident", back_populates="workflow_logs")
    actor = db.relationship("User", lazy="joined")


class ActionItem(TimestampMixin, db.Model):
    __tablename__ = "action_items"
    id = db.Column(db.Integer, primary_key=True)
    incident_id = db.Column(db.Integer, db.ForeignKey("incidents.id", ondelete="CASCADE"), nullable=False, index=True)
    department_id = db.Column(db.Integer, db.ForeignKey("departments.id"), nullable=True)
    owner_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
    kind = db.Column(db.String(30), default="Corrective", nullable=False)
    description = db.Column(db.Text, nullable=False)
    strength = db.Column(db.String(20), default="Strong", nullable=False)
    due_at = db.Column(db.DateTime)
    status = db.Column(db.String(20), default="OPEN", nullable=False, index=True)
    completion_notes = db.Column(db.Text)
    completed_at = db.Column(db.DateTime)
    incident = db.relationship("Incident", back_populates="action_items")
    department = db.relationship("Department", lazy="joined")
    owner = db.relationship("User", lazy="joined")


class RCA(TimestampMixin, db.Model):
    __tablename__ = "rca_records"
    id = db.Column(db.Integer, primary_key=True)
    incident_id = db.Column(db.Integer, db.ForeignKey("incidents.id", ondelete="CASCADE"), unique=True, nullable=False)
    team_members = db.Column(db.Text, nullable=False)
    problem_statement = db.Column(db.Text, nullable=False)
    analysis = db.Column(db.Text, nullable=False)
    root_causes = db.Column(db.Text, nullable=False)
    recommendations = db.Column(db.Text, nullable=False)
    completed_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    completed_at = db.Column(db.DateTime, default=utcnow, nullable=False)
    incident = db.relationship("Incident", backref=db.backref("rca", uselist=False, cascade="all, delete-orphan"))
    completed_by = db.relationship("User", lazy="joined")


class Attachment(db.Model):
    __tablename__ = "attachments"
    id = db.Column(db.Integer, primary_key=True)
    incident_id = db.Column(db.Integer, db.ForeignKey("incidents.id", ondelete="CASCADE"), nullable=False, index=True)
    uploaded_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    original_name = db.Column(db.String(255), nullable=False)
    stored_name = db.Column(db.String(255), unique=True, nullable=False)
    mime_type = db.Column(db.String(120))
    size_bytes = db.Column(db.Integer, nullable=False)
    created_at = db.Column(db.DateTime, default=utcnow, nullable=False)
    incident = db.relationship("Incident", back_populates="attachments")
    uploaded_by = db.relationship("User", lazy="joined")


class Comment(db.Model):
    __tablename__ = "comments"
    id = db.Column(db.Integer, primary_key=True)
    incident_id = db.Column(db.Integer, db.ForeignKey("incidents.id", ondelete="CASCADE"), nullable=False, index=True)
    author_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    body = db.Column(db.Text, nullable=False)
    is_qps_only = db.Column(db.Boolean, default=False, nullable=False)
    created_at = db.Column(db.DateTime, default=utcnow, nullable=False)
    incident = db.relationship("Incident", back_populates="comments")
    author = db.relationship("User", lazy="joined")


class Notification(db.Model):
    __tablename__ = "notifications"
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
    title = db.Column(db.String(180), nullable=False)
    message = db.Column(db.Text, nullable=False)
    incident_id = db.Column(db.Integer, db.ForeignKey("incidents.id", ondelete="CASCADE"), nullable=True)
    read_at = db.Column(db.DateTime)
    created_at = db.Column(db.DateTime, default=utcnow, nullable=False)
    user = db.relationship("User", lazy="joined")
    incident = db.relationship("Incident")


class AuditLog(db.Model):
    __tablename__ = "audit_logs"
    id = db.Column(db.Integer, primary_key=True)
    actor_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True, index=True)
    action = db.Column(db.String(120), nullable=False, index=True)
    entity_type = db.Column(db.String(80), nullable=False)
    entity_id = db.Column(db.String(80))
    details = db.Column(db.Text)
    ip_address = db.Column(db.String(64))
    created_at = db.Column(db.DateTime, default=utcnow, nullable=False, index=True)
    actor = db.relationship("User", lazy="joined")
