#!/usr/bin/env python3
"""Local synthetic mortgage preparation queue. No external integrations.

Download a process fixture from its guide and run twice with the same database.
Every readiness value is supplied by the fixture. A real adapter must establish
these values from verified evidence and the lender's approved rules.
"""
import argparse
import hashlib
import json
import sqlite3
from pathlib import Path


def digest(value):
    raw = json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False)
    return hashlib.sha256(raw.encode("utf-8")).hexdigest()


def same_value(left, right):
    # Python considers True == 1. Evidence checks must keep their types distinct.
    return left is not None and type(left) is type(right) and left == right


def evaluate(fields, rules, schema):
    failures = []
    if not schema:
        return ["missing_field_schema"]
    for name, kind in schema.items():
        value = fields.get(name)
        valid = (
            (kind == "boolean" and type(value) is bool)
            or (kind == "integer" and type(value) is int)
            or (kind == "number" and type(value) in (int, float))
            or (kind == "string" and type(value) is str and bool(value.strip()))
        )
        if not valid:
            failures.append("field_type:" + name)

    if not rules:
        return ["missing_readiness_rules"]
    for rule in rules:
        if rule.get("kind") == "equals":
            field = rule["field"]
            if field not in fields or not same_value(fields[field], rule["value"]):
                failures.append("check:" + field)
        elif rule.get("kind") == "equal_fields":
            left, right = rule["left"], rule["right"]
            if left not in fields or right not in fields or not same_value(fields[left], fields[right]):
                failures.append("compare:" + left + ":" + right)
        else:
            failures.append("unsupported_rule")
    return failures


def run(fixture, database):
    process, policy = fixture["process_id"], fixture["policy_version"]
    if not isinstance(process, str) or not process.strip() or not isinstance(policy, str) or not policy.strip():
        raise ValueError("A nonempty process_id and policy_version are required")
    # Pin the entire rule pack. Reusing a policy label with different rules fails.
    rule_hash = digest({"rules": fixture["rules"], "field_schema": fixture["field_schema"]})
    report = {"process_id": process, "new_receipts": 0, "replay_hits": 0,
              "conflicting_replays": 0, "external_actions": 0, "records": []}
    with sqlite3.connect(database) as db:
        db.execute("""CREATE TABLE IF NOT EXISTS policies (
            process TEXT, policy TEXT, rule_hash TEXT,
            PRIMARY KEY (process, policy))""")
        db.execute("""CREATE TABLE IF NOT EXISTS receipts (
            process TEXT, policy TEXT, event_id TEXT, input_hash TEXT,
            original_status TEXT, status TEXT, reasons TEXT, payload TEXT,
            PRIMARY KEY (process, policy, event_id))""")
        db.execute("""CREATE TABLE IF NOT EXISTS conflicts (
            process TEXT, policy TEXT, event_id TEXT, input_hash TEXT,
            payload TEXT, reason TEXT,
            PRIMARY KEY (process, policy, event_id, input_hash))""")
        db.commit()
        # One transaction covers lookup, receipt persistence and queue status.
        db.execute("BEGIN IMMEDIATE")
        pinned = db.execute("SELECT rule_hash FROM policies WHERE process=? AND policy=?",
                            (process, policy)).fetchone()
        if pinned and pinned[0] != rule_hash:
            raise ValueError("Policy label reused with changed rules. Supply a new policy_version.")
        db.execute("INSERT OR IGNORE INTO policies VALUES (?,?,?)", (process, policy, rule_hash))
        for record in fixture["records"]:
            event_id, fields = record["id"], record["fields"]
            if not isinstance(event_id, str) or not event_id.strip() or not isinstance(fields, dict):
                raise ValueError("Each record needs a nonempty string id and object fields")
            payload = json.dumps(fields, sort_keys=True, allow_nan=False)
            input_hash = digest(fields)
            key = (process, policy, event_id)
            prior = db.execute("""SELECT input_hash,status,reasons FROM receipts
                WHERE process=? AND policy=? AND event_id=?""", key).fetchone()
            if prior and prior[0] != input_hash:
                status, reasons = "hold", ["conflicting_replay"]
                db.execute("INSERT OR IGNORE INTO conflicts VALUES (?,?,?,?,?,?)",
                           (*key, input_hash, payload, reasons[0]))
                db.execute("""UPDATE receipts SET status=?,reasons=?
                    WHERE process=? AND policy=? AND event_id=?""",
                           (status, json.dumps(reasons), *key))
                report["conflicting_replays"] += 1
                # Original evidence is preserved. Replays cannot clear this hold.
            elif prior:
                status, reasons = prior[1], json.loads(prior[2])
                report["replay_hits"] += 1
            else:
                reasons = evaluate(fields, fixture["rules"], fixture["field_schema"])
                status = "hold" if reasons else "ready_for_review"
                db.execute("INSERT INTO receipts VALUES (?,?,?,?,?,?,?,?)",
                           (*key, input_hash, status, status, json.dumps(reasons), payload))
                report["new_receipts"] += 1
            report["records"].append({"id": event_id, "status": status, "reasons": reasons})
        db.commit()
        queue = db.execute("SELECT status,COUNT(*) FROM receipts WHERE process=? AND policy=? GROUP BY status",
                           (process, policy)).fetchall()
        report["current_queue"] = dict(queue)
        report["unique_receipts"] = sum(report["current_queue"].values())
        report["persistent_conflicts"] = db.execute(
            "SELECT COUNT(*) FROM conflicts WHERE process=? AND policy=?", (process, policy)).fetchone()[0]
    return report


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--fixture", required=True, type=Path)
    parser.add_argument("--db", required=True, type=Path)
    args = parser.parse_args()
    fixture = json.loads(args.fixture.read_text(encoding="utf-8"))
    print(json.dumps(run(fixture, args.db), indent=2))


if __name__ == "__main__":
    main()
