from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import uuid
import json

db = SQLAlchemy()

def generate_id(prefix):
    return f"{prefix}-{uuid.uuid4().hex[:8]}"

def coerce_bool(v):
    if v is None:
        return None
    if isinstance(v, bool):
        return v
    if isinstance(v, str):
        return v.strip().lower() in ('true', '1', 'yes')
    return bool(v)

class Rule(db.Model):
    __tablename__ = 'bre_rules'

    id = db.Column(db.String(50), primary_key=True, default=lambda: generate_id('rule'))
    key = db.Column(db.String(100), nullable=False)
    description = db.Column(db.String(255))
    value = db.Column(db.JSON)
    priority = db.Column(db.Integer)
    status = db.Column(db.String(20))
    fallback = db.Column(db.JSON, nullable=True)

    def to_dict(self):
        mapping = PolicyRuleMapping.query.filter_by(rule_id=self.id).first()
        #print(mapping.data if mapping else None)
        return {
            "id": self.id,
            "key": self.key,
            "description": self.description,
            "value": self.value,
            "priority": self.priority,
            "status": self.status,
            "fallback": self.fallback,
            "data": json.loads(mapping.data) if mapping and mapping.data else None
        }


class Policy(db.Model):
    __tablename__ = 'bre_policies'

    id = db.Column(db.String(50), primary_key=True, default=lambda: generate_id('policy'))
    name = db.Column(db.String(100), nullable=False)
    description = db.Column(db.String(255))
    status = db.Column(db.String(20))
    is_base_policy = db.Column(db.Boolean)
    is_live = db.Column(db.Boolean, default=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)


    def to_dict(self):
        rules = []
        for mapping in self.rule_mappings:
            rule_dict = mapping.rule.to_dict()
            # Override with THIS policy's mapping data (rule.to_dict picks an arbitrary mapping)
            try:
                rule_dict['data'] = json.loads(mapping.data) if mapping.data else None
            except (TypeError, ValueError):
                rule_dict['data'] = None
            rules.append(rule_dict)

        linked_policies = [
            {
                **mapping.policy.to_dict(),
                "priority": mapping.priority
            }
            for mapping in self.policies_linked_to_this_base
        ]
        
        return {
            "id": self.id,
            "name": self.name,
            "description": self.description,
            "isBasePolicy": coerce_bool(self.is_base_policy),
            "isLive": coerce_bool(self.is_live),
            "status": self.status,
            "createdAt": self.created_at.isoformat() + "Z",
            "updatedAt": self.updated_at.isoformat() + "Z",
            "assignedPolicies": linked_policies,
            "rules": rules
        }

class PolicyRuleMapping(db.Model):
    __tablename__ = 'policy_rule_mapping'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    policy_id = db.Column(db.String(50), db.ForeignKey('bre_policies.id'), nullable=False)
    rule_id = db.Column(db.String(50), db.ForeignKey('bre_rules.id'), nullable=False)
    data = db.Column(db.Text, nullable=False)

    policy = db.relationship('Policy', backref=db.backref('rule_mappings', cascade='all, delete-orphan'))
    rule = db.relationship('Rule')

class BasePolicyMapping(db.Model):
    __tablename__ = 'base_policy_mapping'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    base_policy_id = db.Column(db.String(50), db.ForeignKey('bre_policies.id'), nullable=False)
    policy_id = db.Column(db.String(50), db.ForeignKey('bre_policies.id'), nullable=False)
    priority = db.Column(db.Integer, nullable=True)

    # The policy that uses the base policy
    policy = db.relationship('Policy', foreign_keys=[policy_id], backref='used_base_mappings')

    # The base policy itself
    base_policy = db.relationship('Policy', foreign_keys=[base_policy_id], backref='policies_linked_to_this_base')


class LiveConfig(db.Model):
    __tablename__ = 'live_config'

    id = db.Column(db.Integer, primary_key=True)
    api_key = db.Column(db.String(255), nullable=True)
    api_secret_hash = db.Column(db.String(255), nullable=True)
    ip_whitelist = db.Column(db.Text, nullable=True)
    enabled = db.Column(db.Boolean, default=False)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    def to_dict(self, secret_revealed=None):
        out = {
            "apiKey": self.api_key,
            "hasSecret": bool(self.api_secret_hash),
            "ipWhitelist": self.ip_whitelist or "",
            "enabled": coerce_bool(self.enabled),
            "updatedAt": (self.updated_at.isoformat() + "Z") if self.updated_at else None,
        }
        if secret_revealed:
            out["secret"] = secret_revealed
        return out


class LiveApiLog(db.Model):
    __tablename__ = 'live_api_log'

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    policy_name = db.Column(db.String(255))
    ip_address = db.Column(db.String(45))
    status_code = db.Column(db.Integer)
    error = db.Column(db.Text, nullable=True)
    requested_at = db.Column(db.DateTime, default=datetime.utcnow)

    def to_dict(self):
        return {
            "id": self.id,
            "policyName": self.policy_name,
            "ip": self.ip_address,
            "status": self.status_code,
            "error": self.error,
            "requestedAt": (self.requested_at.isoformat() + "Z") if self.requested_at else None,
        }


def _strip_condition_from_source_item(item):
    """TEMPORARY: drop `condition` from a source item's details. Revert by removing this and
    its call site below."""
    if not isinstance(item, dict):
        return item
    details = item.get("details")
    if not isinstance(details, dict) or "condition" not in details:
        return item
    return {**item, "details": {k: v for k, v in details.items() if k != "condition"}}


def transform_rule_for_bre(rule):
    """Python mirror of src/lib/breExport.ts transformRuleForBRE."""
    value = rule.value or {}
    src = value.get("source", []) if isinstance(value, dict) else []
    return {
        "key": rule.key,
        "description": [rule.description],
        "value": {
            # TEMPORARY: condition stripped from source items only.
            "source": [_strip_condition_from_source_item(s) for s in src],
            "against": value.get("against", []) if isinstance(value, dict) else [],
        },
        "priority": rule.priority,
    }


def build_grade_rules(policy):
    """Python mirror of src/lib/breExport.ts buildGradeRules."""
    out = {}
    if coerce_bool(policy.is_base_policy):
        mappings = sorted(
            policy.policies_linked_to_this_base,
            key=lambda m: m.priority if m.priority is not None else 0,
        )
        for mapping in mappings:
            sub = mapping.policy
            sub_rules = [transform_rule_for_bre(rm.rule) for rm in sub.rule_mappings]
            if not sub_rules:
                continue
            out[sub.name] = {"rule": sub_rules}
    else:
        rules = [transform_rule_for_bre(rm.rule) for rm in policy.rule_mappings]
        if rules:
            out[policy.name] = {"rule": rules}
    return out