from flask import Blueprint, request, jsonify
from auth import require_creditfair
import json
from models import db, Policy, Rule, PolicyRuleMapping, BasePolicyMapping, coerce_bool

bp = Blueprint('policies', __name__)

@bp.route('/api/policies', methods=['GET'])
@require_creditfair
def get_policies():
    policies = Policy.query.all()
    return jsonify([p.to_dict() for p in policies]), 200

@bp.route('/api/policies', methods=['POST'])
@require_creditfair
def create_policy():
    data = request.get_json()
    try:
        name = data['name']
        if Policy.query.filter_by(name=name).first():
            return jsonify({
                "error": "Conflict",
                "message": f"Policy name '{name}' already exists"
            }), 409

        policy = Policy(
            name=name,
            description=data['description'],
            status=data['status'],
            is_base_policy=coerce_bool(data.get('isBasePolicy', data.get('is_base_policy'))),
        )
        db.session.add(policy)
        db.session.flush()  # Get the policy ID before commit

        rule_ids = data.get('rules', [])
        for rule_id in rule_ids:
            rule = Rule.query.get(rule_id)
            if rule:
                mapping = PolicyRuleMapping(policy_id=policy.id, rule_id=rule.id)
                db.session.add(mapping)

        db.session.commit()
        return jsonify(policy.to_dict()), 201
    except KeyError as e:
        return jsonify({"error": "Invalid request data", "message": f"Missing required field: {str(e)}"}), 400

@bp.route('/api/policies/<id>', methods=['PUT'])
@require_creditfair
def update_policy(id):
    policy = Policy.query.get(id)
    if not policy:
        return jsonify({"error": "Resource not found", "message": f"Policy with id '{id}' not found"}), 404

    data = request.get_json()
    new_name = data.get('name', policy.name)
    if new_name != policy.name:
        clash = Policy.query.filter(Policy.name == new_name, Policy.id != id).first()
        if clash:
            return jsonify({
                "error": "Conflict",
                "message": f"Policy name '{new_name}' already exists"
            }), 409
    policy.name = new_name
    policy.description = data.get('description', policy.description)
    policy.status = data.get('status', policy.status)
    if 'isBasePolicy' in data:
        policy.is_base_policy = coerce_bool(data['isBasePolicy'])
    elif 'is_base_policy' in data:
        policy.is_base_policy = coerce_bool(data['is_base_policy'])
    db.session.commit()
    return jsonify(policy.to_dict()), 200

@bp.route('/api/policies/<id>', methods=['DELETE'])
@require_creditfair
def delete_policy(id):
    policy = Policy.query.get(id)
    if not policy:
        return jsonify({"error": "Resource not found", "message": f"Policy with id '{id}' not found"}), 404

    db.session.delete(policy)
    db.session.commit()
    return '', 204

@bp.route('/api/policies/<id>/rules', methods=['POST'])
@require_creditfair
def add_rule_to_policy(id):
    policy = Policy.query.get(id)
    if not policy:
        return jsonify({"error": "Resource not found", "message": f"Policy with id '{id}' not found"}), 404

    if coerce_bool(policy.is_base_policy):
        return jsonify({
            "error": "Invalid operation",
            "message": "Rules cannot be attached to a base policy"
        }), 400

    data = request.get_json()
    rule = Rule(
        key=data['key'],
        description=data['description'],
        value=data['value'],
        priority=data['priority'],
        status=data['status']
    )
    db.session.add(rule)
    db.session.flush()

    mapping = PolicyRuleMapping(policy_id=policy.id, rule_id=rule.id)
    db.session.add(mapping)

    db.session.commit()
    return jsonify(rule.to_dict()), 201

@bp.route('/api/policies/<policy_id>/rules/<rule_id>', methods=['POST'])
@require_creditfair
def assign_rule_to_policy(policy_id, rule_id):
    policy = Policy.query.get(policy_id)
    rule = Rule.query.get(rule_id)

    data = request.get_json()

    dataItem = data.get('data', {})

    if not policy:
        return jsonify({
            "error": "Resource not found",
            "message": f"Policy with id '{policy_id}' not found"
        }), 404

    if not rule:
        return jsonify({
            "error": "Resource not found",
            "message": f"Rule with id '{rule_id}' not found"
        }), 404

    if coerce_bool(policy.is_base_policy):
        return jsonify({
            "error": "Invalid operation",
            "message": "Rules cannot be attached to a base policy"
        }), 400

    # Check for existing mapping
    existing = PolicyRuleMapping.query.filter_by(policy_id=policy_id, rule_id=rule_id).first()
    if existing:
        
        if existing.data != json.dumps(dataItem):
            existing.data = json.dumps(dataItem)
            db.session.commit()
            return jsonify(rule.to_dict()), 200


        return jsonify({
            "error": "Conflict",
            "message": "Rule is already assigned to this policy"
        }), 409

    mapping = PolicyRuleMapping(policy_id=policy_id, rule_id=rule_id, data=json.dumps(dataItem))
    db.session.add(mapping)
    db.session.commit()

    return jsonify(rule.to_dict()), 201


@bp.route('/api/policies/<policy_id>/rules/<rule_id>', methods=['DELETE'])
@require_creditfair
def remove_rule_from_policy(policy_id, rule_id):
    policy = Policy.query.get(policy_id)
    if not policy:
        return jsonify({
            "error": "Resource not found",
            "message": f"Policy with id '{policy_id}' not found"
        }), 404

    mapping = PolicyRuleMapping.query.filter_by(policy_id=policy_id, rule_id=rule_id).first()
    if not mapping:
        return jsonify({
            "error": "Resource not found",
            "message": f"Rule '{rule_id}' is not assigned to policy '{policy_id}'"
        }), 404

    db.session.delete(mapping)
    db.session.commit()
    return jsonify(policy.to_dict()), 200


@bp.route('/api/policies/<base_policy_id>/assign-policy', methods=['POST'])
@require_creditfair
def assign_policy_to_base_policy(base_policy_id):
    policy = Policy.query.get(base_policy_id)
    if not policy:
        return jsonify({
            "error": "Resource not found",
            "message": f"Base Policy with id '{base_policy_id}' not found"
        }), 404

    data = request.get_json()
    # Check for existing mapping
    existing = BasePolicyMapping.query.filter_by(base_policy_id=base_policy_id, policy_id=data['policyId']).first()
    if existing:
        return jsonify({
            "error": "Conflict",
            "message": "Policy is already assigned to this policy"
        }), 409

    mapping = BasePolicyMapping(
        base_policy_id=base_policy_id,
        policy_id=data['policyId'],
        priority=data.get('priority'),
    )
    db.session.add(mapping)
    db.session.commit()

    return jsonify(policy.to_dict()), 201

@bp.route('/api/policies/<basePolicyId>/assigned-policies/<policyId>', methods=['DELETE'])
@require_creditfair
def delete_policy_mapping(basePolicyId, policyId):
    base_policy = Policy.query.get(basePolicyId)
    if not base_policy:
        return jsonify({
            "error": "Resource not found",
            "message": f"Base Policy with id '{basePolicyId}' not found"
        }), 404

    mapping = BasePolicyMapping.query.filter_by(base_policy_id=basePolicyId, policy_id=policyId).first()
    if not mapping:
        return jsonify({
            "error": "Resource not found",
            "message": f"Policy '{policyId}' is not assigned to base policy '{basePolicyId}'"
        }), 404

    db.session.delete(mapping)
    db.session.commit()
    return jsonify(base_policy.to_dict()), 200

@bp.route('/api/policies/<basePolicyId>/assigned-policies/<policyId>', methods=['PUT'])
@require_creditfair
def update_mapped_policy_attr(basePolicyId, policyId):
    base_policy = Policy.query.get(basePolicyId)
    if not base_policy:
        return jsonify({
            "error": "Resource not found",
            "message": f"Base Policy with id '{basePolicyId}' not found"
        }), 404

    mapping = BasePolicyMapping.query.filter_by(base_policy_id=basePolicyId, policy_id=policyId).first()
    if not mapping:
        return jsonify({
            "error": "Resource not found",
            "message": f"Policy '{policyId}' is not assigned to base policy '{basePolicyId}'"
        }), 404

    mapping.priority = request.json.get('priority', mapping.priority)
    db.session.commit()
    return jsonify(base_policy.to_dict()), 200