"""Local integration components. Trusted authentication and approved release required.

No broker order, billing provider, public listener or live-capital clearance is supplied.
"""
import hashlib
import hmac
import json
import secrets
import sqlite3
import time
import base64
import re
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path


class AccessDenied(ValueError):
    pass


def utc(value):
    parsed = datetime.fromisoformat(value.replace('Z', '+00:00'))
    if parsed.tzinfo is None:
        raise ValueError('Timezone-aware timestamp required')
    return parsed.astimezone(timezone.utc)


@dataclass(frozen=True)
class Principal:
    """Created by trusted session middleware, never from request JSON or headers."""
    subject: str
    tenant_id: int
    session_id: str
    representative: bool = False
    admin: bool = False


class LicenseControl:
    def __init__(self, db_path, terms_text, version, *, pepper, approved=False,
                 approval_reference=None, clock=None, tenant_symbols=None):
        if not isinstance(pepper, bytes) or len(pepper) < 32:
            raise ValueError('Independent secret of at least 32 bytes required')
        if not terms_text.strip() or not version.strip():
            raise ValueError('Terms and version required')
        if approved and not approval_reference:
            raise ValueError('Version-specific approval reference required')
        self.path = str(db_path)
        self.text, self.version = terms_text, version
        self.digest = hashlib.sha256(terms_text.encode()).hexdigest()
        self.pepper, self.approved = pepper, approved
        self.approval_reference = approval_reference
        self.clock = clock or (lambda: datetime.now(timezone.utc))
        self.tenant_symbols = tenant_symbols or {}

    def _open(self):
        target=Path(self.path).resolve()
        if not target.is_file():
            raise AccessDenied('Existing commercial registry required')
        c = sqlite3.connect(target.as_uri()+'?mode=rw', uri=True, timeout=2)
        c.row_factory = sqlite3.Row
        c.execute('PRAGMA foreign_keys=ON')
        return c

    @contextmanager
    def connect(self):
        c=self._open()
        try:
            with c:
                yield c
        finally:
            c.close()

    def install(self):
        """Explicit additive migration. Never invoked by the default verifier."""
        if not Path(self.path).exists():
            c=sqlite3.connect(self.path)
            c.close()
        with self.connect() as c:
            c.executescript(Path(__file__).with_name('init_commercial_tenants.sql').read_text())
            c.executescript(Path(__file__).with_name('init_license_controls.sql').read_text())
            old = c.execute('SELECT * FROM license_terms WHERE version=?', (self.version,)).fetchone()
            if old and (old['terms_sha256'] != self.digest or old['terms_text'] != self.text):
                raise ValueError('Terms versions are immutable; issue a new version')
            c.execute('INSERT OR IGNORE INTO license_terms VALUES (?,?,?,?,?)',
                      (self.version,self.digest,self.text,int(self.approved),self.approval_reference))
            # Only the reviewed manifest can enable the current release. Drafts remain blocked.
            if old and self.approved and not old['approved']:
                c.execute('UPDATE license_terms SET approved=1, approval_reference=? WHERE version=?',
                          (self.approval_reference,self.version))

    def _principal(self, p, admin=False):
        if (not isinstance(p, Principal) or not p.subject or not p.session_id or
            (admin and not p.admin)):
            raise AccessDenied('Authenticated authorized principal required')

    def _release(self, c):
        row = c.execute('SELECT * FROM license_terms WHERE version=?',(self.version,)).fetchone()
        if (not self.approved or not self.approval_reference or not row or not row['approved'] or
            row['terms_sha256'] != self.digest or row['approval_reference'] != self.approval_reference):
            raise AccessDenied('Approved current terms unavailable')

    def _event(self, c, tenant, actor, event, reference):
        c.execute('INSERT INTO license_control_events(tenant_id,actor,event,reference,recorded_at) VALUES(?,?,?,?,?)',
                  (tenant,actor,event,reference,self.clock().isoformat()))

    def create_pending(self, p, client, expires_at, fee):
        self._principal(p, admin=True)
        if not client.strip() or utc(expires_at) <= self.clock() or not isinstance(fee,(int,float)) or not 0 <= fee < 1e9:
            raise ValueError('Valid client, future expiry and fee required')
        with self.connect() as c:
            c.execute('BEGIN IMMEDIATE')
            cur = c.execute('INSERT INTO commercial_tenant_licenses(client_name,license_key,license_status,client_custom_risk_pct,client_custom_drawdown_limit,monthly_lease_fee,activated_at,expires_at) VALUES(?,?,\'TRIAL\',1,3,?,?,?)',
                            (client,'pending:'+secrets.token_hex(24),fee,self.clock().isoformat(),expires_at))
            self._event(c,cur.lastrowid,p.subject,'CREATED_PENDING','NO_API_KEY_ISSUED')
            return cur.lastrowid

    def accept(self, p, version, digest, accepted):
        self._principal(p)
        if not p.representative or accepted is not True or version != self.version or digest != self.digest:
            raise AccessDenied('Explicit authorized acceptance of exact current terms required')
        with self.connect() as c:
            c.execute('BEGIN IMMEDIATE')
            self._release(c)
            row=c.execute('SELECT tenant_id FROM commercial_tenant_licenses WHERE tenant_id=?',(p.tenant_id,)).fetchone()
            if not row:
                raise AccessDenied('Tenant unavailable')
            c.execute('INSERT OR IGNORE INTO license_acceptances(tenant_id,subject,session_id,terms_version,terms_sha256,accepted_at) VALUES(?,?,?,?,?,?)',
                      (p.tenant_id,p.subject,p.session_id,self.version,self.digest,self.clock().isoformat()))
            self._event(c,p.tenant_id,p.subject,'TERMS_ACCEPTED',self.digest)
        return {'accepted':True,'terms_version':self.version,'terms_sha256':self.digest,
                'license_activated':False}

    def _eligible(self,c,tenant,active=True):
        self._release(c)
        # A tenant can be terminated before its first payment entitlement exists.
        # Keep that terminal decision independent of subsequent payment records.
        if c.execute("SELECT 1 FROM license_control_events WHERE tenant_id=? AND event='TERMINATED' LIMIT 1",
                     (tenant,)).fetchone():
            raise AccessDenied('License permanently terminated')
        row=c.execute('SELECT * FROM commercial_tenant_licenses WHERE tenant_id=?',(tenant,)).fetchone()
        state=c.execute('SELECT * FROM license_entitlements WHERE tenant_id=?',(tenant,)).fetchone()
        acceptance=c.execute('SELECT 1 FROM license_acceptances WHERE tenant_id=? AND terms_version=? AND terms_sha256=?',
                             (tenant,self.version,self.digest)).fetchone()
        if not row or not state or state['terminated'] or not acceptance:
            raise AccessDenied('License entitlement or acceptance unavailable')
        if active and row['license_status'] != 'ACTIVE':
            raise AccessDenied('License suspended or inactive')
        if utc(row['expires_at']) <= self.clock() or utc(state['paid_until']) <= self.clock():
            raise AccessDenied('License expired or payment entitlement elapsed')
        if not 0 <= row['client_custom_risk_pct'] <= 1 or not 0 <= row['client_custom_drawdown_limit'] <= 3:
            raise AccessDenied('Risk settings exceed service limits')
        return row

    def record_payment_entitlement(self,p,tenant,paid_until,verified_payment_reference):
        self._principal(p,admin=True)
        if utc(paid_until) <= self.clock() or not verified_payment_reference.strip():
            raise ValueError('Verified payment reference and future entitlement required')
        with self.connect() as c:
            c.execute('BEGIN IMMEDIATE')
            c.execute('INSERT INTO license_entitlements(tenant_id,paid_until,payment_reference) VALUES(?,?,?) ON CONFLICT(tenant_id) DO UPDATE SET paid_until=excluded.paid_until,payment_reference=excluded.payment_reference',
                      (tenant,paid_until,verified_payment_reference))
            self._event(c,tenant,p.subject,'PAYMENT_ENTITLEMENT_RECORDED',verified_payment_reference)

    def _key_digest(self,key):
        return hmac.new(self.pepper,key.encode(),hashlib.sha256).hexdigest()

    def activate(self,p,tenant):
        self._principal(p,admin=True)
        with self.connect() as c:
            c.execute('BEGIN IMMEDIATE')
            self._eligible(c,tenant,active=False)
            key='infinbot_'+secrets.token_urlsafe(32)
            c.execute('INSERT INTO commercial_api_credentials(tenant_id,key_digest,revoked) VALUES(?,?,0) ON CONFLICT(tenant_id) DO UPDATE SET key_digest=excluded.key_digest,revoked=0',
                      (tenant,self._key_digest(key)))
            c.execute("UPDATE commercial_tenant_licenses SET license_status='ACTIVE' WHERE tenant_id=?",(tenant,))
            self._event(c,tenant,p.subject,'ACTIVATED_ROTATED_KEY',self.digest)
        # Deliver once over authenticated TLS. Never log or store raw keys.
        return key

    def suspend(self,p,tenant,reason,*,permanent=False):
        self._principal(p,admin=True)
        if not reason.strip():
            raise ValueError('Evidence or payment incident reference required')
        with self.connect() as c:
            c.execute('BEGIN IMMEDIATE')
            changed=c.execute("UPDATE commercial_tenant_licenses SET license_status='SUSPENDED' WHERE tenant_id=?",(tenant,)).rowcount
            if changed != 1:
                raise AccessDenied('Tenant unavailable')
            c.execute('UPDATE commercial_api_credentials SET revoked=1 WHERE tenant_id=?',(tenant,))
            if permanent:
                c.execute('UPDATE license_entitlements SET terminated=1 WHERE tenant_id=?',(tenant,))
            self._event(c,tenant,p.subject,'TERMINATED' if permanent else 'SUSPENDED',reason)

    def status(self,p):
        self._principal(p)
        with self.connect() as c:
            try:
                self._eligible(c,p.tenant_id)
                status='ENTITLEMENT_ELIGIBLE'
            except (AccessDenied,ValueError):
                status='BLOCKED'
        return {'license_access':status,'broker_orders_submitted':0,'live_capital_clearance':False}

    def guarded_send(self,tenant,key,frame,sender):
        """Serialize status check and bounded transport handoff with suspension commits.

        The injected sender must have a short timeout. Bytes already handed off cannot
        be recalled. This is not a broker execution gateway.
        """
        permitted={'symbol','side','units','price','timestamp','sequence'}
        if not isinstance(frame,dict) or set(frame) != permitted:
            raise AccessDenied('Only scoped coordinates may be transmitted')
        if frame['side'] not in ('BUY','SELL') or not isinstance(frame['symbol'],str):
            raise AccessDenied('Invalid coordinates')
        if frame['symbol'] not in self.tenant_symbols.get(tenant, ()):
            raise AccessDenied('Symbol not in reviewed tenant scope')
        from decimal import Decimal, InvalidOperation
        try:
            for field in ('units','price'):
                value=Decimal(str(frame[field]))
                if not value.is_finite() or value <= 0:
                    raise ValueError()
            age=(self.clock()-utc(frame['timestamp'])).total_seconds()
            if not 0 <= age <= 60:
                raise ValueError()
        except (ValueError,TypeError,InvalidOperation):
            raise AccessDenied('Fresh complete signal required') from None
        payload=json.dumps(frame,allow_nan=False).encode()
        if len(payload)>65536:
            raise AccessDenied('Signal too large')
        c=self._open()
        try:
            c.execute('BEGIN IMMEDIATE')
            self._eligible(c,tenant)
            credential=c.execute('SELECT * FROM commercial_api_credentials WHERE tenant_id=?',(tenant,)).fetchone()
            if not credential or credential['revoked'] or not hmac.compare_digest(credential['key_digest'],self._key_digest(key)):
                raise AccessDenied('Invalid or revoked credential')
            sender(payload)
            c.commit()
        finally:
            c.close()

    def stream(self,tenant,key,frames,sender,*,stop,heartbeat_seconds=1):
        """Host adapter calls guarded_send for every frame and checks idle access.

        frames is a nonblocking callable returning a dict or None, never a generator
        that can block forever. stop is a threading.Event controlled by the host.
        """
        if not 0 < heartbeat_seconds <= 1:
            raise ValueError('Heartbeat must be at most one second')
        while not stop.is_set():
            with self.connect() as c:
                self._eligible(c,tenant)
                row=c.execute('SELECT key_digest,revoked FROM commercial_api_credentials WHERE tenant_id=?',(tenant,)).fetchone()
                if not row or row['revoked'] or not hmac.compare_digest(row['key_digest'],self._key_digest(key)):
                    raise AccessDenied('Revoked credential')
            frame=frames()
            if frame is not None:
                self.guarded_send(tenant,key,frame,sender)
            stop.wait(heartbeat_seconds)


class ClickwrapApp:
    """WSGI app; host provides authenticated session middleware and exact TLS origin.

    No authentication fallback. Does not accept license keys or tenant IDs from JSON.
    Admin activation and payment controls are not exposed as public HTTP endpoints.
    """
    def __init__(self,control,*,authenticate=None,origin='https://unconfigured.invalid'):
        self.control,self.authenticate,self.origin=control,authenticate,origin

    def csrf(self,p,bucket=None):
        bucket=int(time.time()//900) if bucket is None else bucket
        msg=f'{p.subject}:{p.tenant_id}:{p.session_id}:{bucket}'
        return f'{bucket}.'+hmac.new(self.control.pepper,msg.encode(),hashlib.sha256).hexdigest()

    def __call__(self,environ,start_response):
        code=200
        try:
            if not self.authenticate:
                raise AccessDenied('Host session authentication is not configured')
            p=self.authenticate(environ)
            self.control._principal(p)
            route=environ.get('PATH_INFO','')
            method=environ.get('REQUEST_METHOD','GET')
            if method=='GET' and route=='/client':
                raw=Path(__file__).with_name('infinbot_client_dashboard.html').read_bytes()
                hashes={tag:["'sha256-"+base64.b64encode(hashlib.sha256(body).digest()).decode()+"'"
                             for body in re.findall(b'<'+tag.encode()+b'>(.*?)</'+tag.encode()+b'>',raw,re.S)]
                        for tag in ('script','style')}
                csp="default-src 'none'; script-src "+' '.join(hashes['script'])+"; style-src "+' '.join(hashes['style'])+"; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'"
                start_response('200 OK',[('Content-Type','text/html; charset=utf-8'),('Content-Length',str(len(raw))),
                    ('Cache-Control','no-store'),('X-Content-Type-Options','nosniff'),('Referrer-Policy','no-referrer'),
                    ('Content-Security-Policy',csp),('X-Frame-Options','DENY')])
                return [raw]
            elif method=='GET' and route=='/api/terms':
                payload={'text':self.control.text,'version':self.control.version,'sha256':self.control.digest,
                         'approved':self.control.approved,'csrf':self.csrf(p)}
            elif method=='GET' and route=='/api/license':
                payload=self.control.status(p)
            elif method=='POST' and route=='/api/accept':
                if environ.get('HTTP_ORIGIN') != self.origin:
                    raise AccessDenied('Same-origin request required')
                token=environ.get('HTTP_X_CSRF_TOKEN','')
                if not hmac.compare_digest(token,self.csrf(p)):
                    raise AccessDenied('Invalid or expired session CSRF token')
                size=int(environ.get('CONTENT_LENGTH','0'))
                if not 0 < size <= 4096 or environ.get('CONTENT_TYPE') != 'application/json':
                    raise ValueError()
                data=json.loads(environ['wsgi.input'].read(size))
                if not isinstance(data,dict) or set(data)!={'version','sha256','accepted'}:
                    raise ValueError()
                payload=self.control.accept(p,data['version'],data['sha256'],data['accepted'])
            else:
                code,payload=404,{'error':'Endpoint not found'}
        except AccessDenied:
            code,payload=403,{'error':'Access blocked or acceptance prerequisites missing'}
        except (ValueError,KeyError,TypeError):
            code,payload=400,{'error':'Invalid request'}
        except (sqlite3.Error,OSError):
            code,payload=503,{'error':'License registry unavailable; access blocked'}
        statuses={200:'OK',400:'Bad Request',403:'Forbidden',404:'Not Found',503:'Service Unavailable'}
        start_response(f'{code} {statuses[code]}',[('Content-Type','application/json'),('Cache-Control','no-store'),
                                                ('X-Content-Type-Options','nosniff')])
        return [json.dumps(payload,allow_nan=False).encode()]
