"""Gate 7B deterministic approval for memory writes.

Gate 7B is the first gate that may persist learned-recommended memory
candidates, but the learned output still supplies only a candidate ID. The
deterministic approval layer revalidates the candidate, verifies source trace
hashes, writes only evidence-backed ``ConsolidatedMemoryRecord`` objects under
the requested state directory, and supports invalidating bad records.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Any

from .bytecode import CognitiveOp
from .consolidation import ConsolidatedMemoryRecord, ConsolidatedMemoryStore, MemoryRetriever, SUPPORTED_MEMORY_TYPES
from .ledger import TraceLedgerRecord, stable_hash
from .memory_recommendation import MemoryWriteCandidate, validate_memory_write_candidate


GATE7B_SCHEMA_VERSION = 1
DETERMINISTIC_MEMORY_WRITE_APPROVER = "deterministic_memory_write_approver_v1"


@dataclass(slots=True)
class MemoryWriteApprovalDecision:
    approval_id: str
    candidate_id: str
    approved: bool
    reason_code: str
    evidence_verified: bool
    reversible: bool = True
    deterministic_approver: str = DETERMINISTIC_MEMORY_WRITE_APPROVER

    def to_dict(self) -> dict[str, Any]:
        return {
            "approval_id": self.approval_id,
            "candidate_id": self.candidate_id,
            "approved": self.approved,
            "reason_code": self.reason_code,
            "evidence_verified": self.evidence_verified,
            "reversible": self.reversible,
            "deterministic_approver": self.deterministic_approver,
            "schema_version": GATE7B_SCHEMA_VERSION,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "MemoryWriteApprovalDecision":
        return cls(
            approval_id=str(data["approval_id"]),
            candidate_id=str(data.get("candidate_id", "")),
            approved=bool(data["approved"]),
            reason_code=str(data.get("reason_code", "")),
            evidence_verified=bool(data.get("evidence_verified", False)),
            reversible=bool(data.get("reversible", True)),
            deterministic_approver=str(data.get("deterministic_approver", DETERMINISTIC_MEMORY_WRITE_APPROVER)),
        )


@dataclass(slots=True)
class MemoryWriteExecutionResult:
    write_id: str
    candidate_id: str
    memory_id: str
    approved: bool
    written: bool
    invalidated: bool
    reason_code: str
    evidence_verified: bool
    memory_type: str
    source_trace_count: int
    store_path: str
    approval: MemoryWriteApprovalDecision

    def to_dict(self) -> dict[str, Any]:
        return {
            "write_id": self.write_id,
            "candidate_id": self.candidate_id,
            "memory_id": self.memory_id,
            "approved": self.approved,
            "written": self.written,
            "invalidated": self.invalidated,
            "reason_code": self.reason_code,
            "evidence_verified": self.evidence_verified,
            "memory_type": self.memory_type,
            "source_trace_count": self.source_trace_count,
            "store_path": self.store_path,
            "approval": self.approval.to_dict(),
            "schema_version": GATE7B_SCHEMA_VERSION,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "MemoryWriteExecutionResult":
        return cls(
            write_id=str(data["write_id"]),
            candidate_id=str(data.get("candidate_id", "")),
            memory_id=str(data.get("memory_id", "")),
            approved=bool(data["approved"]),
            written=bool(data["written"]),
            invalidated=bool(data.get("invalidated", False)),
            reason_code=str(data.get("reason_code", "")),
            evidence_verified=bool(data.get("evidence_verified", False)),
            memory_type=str(data.get("memory_type", "")),
            source_trace_count=int(data.get("source_trace_count", 0)),
            store_path=str(data.get("store_path", "")),
            approval=MemoryWriteApprovalDecision.from_dict(dict(data["approval"])),
        )


@dataclass(slots=True)
class MemoryWriteImpactAudit:
    approved_memory_write_count: int
    active_memory_write_count: int
    unsupported_memory_write_count: int
    rejected_unsupported_write_count: int
    evidence_coverage: float
    invalidated_bad_memory_count: int
    retrieval_precision: float
    regression_rate: float
    memory_store_write_count: int
    evidence_hash_mismatch_rejected_count: int

    def to_dict(self) -> dict[str, Any]:
        return {
            "approved_memory_write_count": self.approved_memory_write_count,
            "active_memory_write_count": self.active_memory_write_count,
            "unsupported_memory_write_count": self.unsupported_memory_write_count,
            "rejected_unsupported_write_count": self.rejected_unsupported_write_count,
            "evidence_coverage": self.evidence_coverage,
            "invalidated_bad_memory_count": self.invalidated_bad_memory_count,
            "retrieval_precision": self.retrieval_precision,
            "regression_rate": self.regression_rate,
            "memory_store_write_count": self.memory_store_write_count,
            "evidence_hash_mismatch_rejected_count": self.evidence_hash_mismatch_rejected_count,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "MemoryWriteImpactAudit":
        return cls(
            approved_memory_write_count=int(data["approved_memory_write_count"]),
            active_memory_write_count=int(data["active_memory_write_count"]),
            unsupported_memory_write_count=int(data["unsupported_memory_write_count"]),
            rejected_unsupported_write_count=int(data["rejected_unsupported_write_count"]),
            evidence_coverage=float(data["evidence_coverage"]),
            invalidated_bad_memory_count=int(data["invalidated_bad_memory_count"]),
            retrieval_precision=float(data["retrieval_precision"]),
            regression_rate=float(data["regression_rate"]),
            memory_store_write_count=int(data["memory_store_write_count"]),
            evidence_hash_mismatch_rejected_count=int(data["evidence_hash_mismatch_rejected_count"]),
        )


class DeterministicMemoryWriteApprover:
    def approve(
        self,
        candidate: MemoryWriteCandidate,
        trace_index: dict[str, TraceLedgerRecord],
        action_mask: dict[str, bool] | None = None,
    ) -> MemoryWriteApprovalDecision:
        valid, reason = validate_memory_write_candidate(candidate)
        evidence_verified, evidence_reason = verify_memory_candidate_evidence(candidate, trace_index)
        approved = valid and evidence_verified and bool((action_mask or {CognitiveOp.STORE_EPISODE.value: True}).get(CognitiveOp.STORE_EPISODE.value, False))
        if not valid:
            reason_code = reason
        elif not evidence_verified:
            reason_code = evidence_reason
        elif not bool((action_mask or {CognitiveOp.STORE_EPISODE.value: True}).get(CognitiveOp.STORE_EPISODE.value, False)):
            reason_code = "action_mask_violation"
        else:
            reason_code = "approved_for_deterministic_memory_write"
        approval_payload = {
            "candidate_id": candidate.candidate_id,
            "candidate_hash": stable_hash(candidate.to_dict()),
            "evidence_verified": evidence_verified,
            "reason_code": reason_code,
            "approved": approved,
            "approver": DETERMINISTIC_MEMORY_WRITE_APPROVER,
        }
        return MemoryWriteApprovalDecision(
            approval_id="memwrite_approval_" + stable_hash(approval_payload)[:20],
            candidate_id=candidate.candidate_id,
            approved=approved,
            reason_code=reason_code,
            evidence_verified=evidence_verified,
        )


class DeterministicMemoryWriteStore:
    def __init__(self, store_path: str | Path, *, approver: DeterministicMemoryWriteApprover | None = None) -> None:
        self.store_path = Path(store_path)
        self.store = ConsolidatedMemoryStore(self.store_path)
        self.approver = approver or DeterministicMemoryWriteApprover()
        self.memory_store_write_count = 0

    def write_candidate(
        self,
        *,
        write_id: str,
        candidate: MemoryWriteCandidate,
        trace_index: dict[str, TraceLedgerRecord],
        action_mask: dict[str, bool] | None = None,
        invalidate_after_write: bool = False,
        invalidation_reason: str = "",
    ) -> MemoryWriteExecutionResult:
        approval = self.approver.approve(candidate, trace_index, action_mask)
        record = candidate.to_memory_record() if approval.approved else None
        if record is not None and invalidate_after_write:
            record.status = "invalidated"
            record.invalidated_by = invalidation_reason or "gate7b_invalidated_bad_memory_probe"
        if record is not None:
            records = _upsert_record(self.store.read_all(), record)
            self.store.write_all(records)
            self.memory_store_write_count += 1
        return MemoryWriteExecutionResult(
            write_id=write_id,
            candidate_id=candidate.candidate_id,
            memory_id=record.memory_id if record is not None else "",
            approved=approval.approved,
            written=record is not None,
            invalidated=bool(record is not None and record.status == "invalidated"),
            reason_code=approval.reason_code,
            evidence_verified=approval.evidence_verified,
            memory_type=candidate.memory_type,
            source_trace_count=len(candidate.source_trace_ids),
            store_path=str(self.store_path),
            approval=approval,
        )


def verify_memory_candidate_evidence(candidate: MemoryWriteCandidate, trace_index: dict[str, TraceLedgerRecord]) -> tuple[bool, str]:
    if len(candidate.source_trace_ids) != len(candidate.source_trace_hashes):
        return False, "source_trace_mismatch"
    source_ids = set(candidate.source_trace_ids)
    for trace_id, expected_hash in zip(candidate.source_trace_ids, candidate.source_trace_hashes):
        trace = trace_index.get(trace_id)
        if trace is None:
            return False, "source_trace_not_found"
        if not trace.verify_hash():
            return False, "source_trace_hash_invalid"
        if trace.content_hash != expected_hash:
            return False, "source_trace_hash_mismatch"
    evidence_trace_ids = {str(item.get("trace_id", "")) for item in candidate.verifier_evidence}
    if not evidence_trace_ids or not evidence_trace_ids <= source_ids:
        return False, "verifier_evidence_not_linked"
    if not all(bool(item.get("success", False)) for item in candidate.verifier_evidence):
        return False, "verifier_evidence_failed"
    return True, "ok"


def build_memory_write_impact_audit(
    *,
    records: list[ConsolidatedMemoryRecord],
    trace_index: dict[str, TraceLedgerRecord],
    results: list[MemoryWriteExecutionResult],
    baseline_records: list[ConsolidatedMemoryRecord] | None = None,
    retrieval_queries: list[str] | None = None,
    memory_store_write_count: int = 0,
) -> MemoryWriteImpactAudit:
    written_records = [record for record in records if _gate7b_record(record)]
    active_records = [record for record in written_records if record.status == "active"]
    invalidated_records = [record for record in written_records if record.status == "invalidated"]
    covered_hashes = {trace_hash for record in written_records for trace_hash in record.source_trace_hashes}
    known_hashes = {trace.content_hash for trace in trace_index.values()}
    queries = retrieval_queries or ["verified approved memory source repair pytest verifier evidence"]
    baseline_success = _retrieval_success_rate(baseline_records or active_records, queries)
    post_success = _retrieval_success_rate(records, queries)
    retriever = MemoryRetriever(records)
    for query in queries:
        retriever.retrieve(query, limit=5)
    return MemoryWriteImpactAudit(
        approved_memory_write_count=sum(1 for result in results if result.approved and result.written),
        active_memory_write_count=len(active_records),
        unsupported_memory_write_count=sum(1 for record in written_records if record.memory_type not in SUPPORTED_MEMORY_TYPES),
        rejected_unsupported_write_count=sum(1 for result in results if result.reason_code == "unsupported_memory_type" and not result.written),
        evidence_coverage=len(covered_hashes & known_hashes) / len(known_hashes) if known_hashes else 0.0,
        invalidated_bad_memory_count=len(invalidated_records),
        retrieval_precision=retriever.report.retrieval_precision,
        regression_rate=0.0 if post_success >= baseline_success else baseline_success - post_success,
        memory_store_write_count=memory_store_write_count,
        evidence_hash_mismatch_rejected_count=sum(
            1
            for result in results
            if result.reason_code in {"source_trace_not_found", "source_trace_hash_invalid", "source_trace_hash_mismatch", "verifier_evidence_not_linked"}
            and not result.written
        ),
    )


def memory_write_approval_artifact_hash(payload: dict[str, Any]) -> str:
    return stable_hash(payload)


def _upsert_record(records: list[ConsolidatedMemoryRecord], record: ConsolidatedMemoryRecord) -> list[ConsolidatedMemoryRecord]:
    by_id = {item.memory_id: item for item in records}
    by_id[record.memory_id] = record
    return [by_id[key] for key in sorted(by_id)]


def _gate7b_record(record: ConsolidatedMemoryRecord) -> bool:
    opportunity_id = str(record.metadata.get("opportunity_id", ""))
    return opportunity_id.startswith("gate7b_")


def _retrieval_success_rate(records: list[ConsolidatedMemoryRecord], queries: list[str]) -> float:
    if not queries:
        return 1.0
    retriever = MemoryRetriever(records)
    success = sum(1 for query in queries if retriever.retrieve(query, limit=3))
    return success / len(queries)
