"""Integración sobre una base MariaDB temporal. No reutiliza datos de desarrollo."""
import http.cookiejar
import json
import os
from pathlib import Path
import socket
import subprocess
import tempfile
import time
import urllib.request
import urllib.error
import uuid

root = Path(__file__).resolve().parents[2]
state = Path(os.environ.get('HOMECORE_DEV_STATE', str(Path.home()/'.local/share/homecore-dev')))
name = 'homecore_test_' + uuid.uuid4().hex
mysql = ['/opt/bitnami/mariadb/bin/mariadb', '--no-defaults', '--socket='+str(state/'mysql.sock'), '-uroot']
def sql(text):
    return subprocess.check_output(mysql+['-N','-e',text], text=True)
class Client:
    def __init__(self):
        self.jar = http.cookiejar.CookieJar()
        self.opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self.jar))
        self.csrf = ''
    def request(self, path, method='GET', data=None, expected=200, token=True):
        headers={'Content-Type':'application/json'}
        if token: headers['X-CSRF-Token']=self.csrf
        request=urllib.request.Request(base+path, data=json.dumps(data).encode() if data is not None else None, headers=headers, method=method)
        try: response=self.opener.open(request)
        except urllib.error.HTTPError as e: response=e
        payload=response.read()
        assert response.code==expected, (path,method,response.code,payload)
        result=json.loads(payload) if payload else {}
        if 'csrf_token' in result: self.csrf=result['csrf_token']
        return result
    def register(self,email):
        self.request('/auth/csrf')
        return self.request('/auth/register','POST',{'email':email,'name':'Prueba <script>','password':'correct-password-123'},201)['user']

sql(f'CREATE DATABASE {name} CHARACTER SET utf8mb4')
server=None
sessions=tempfile.TemporaryDirectory()
try:
    env={**os.environ,'HOMECORE_ENV':'development','HOMECORE_SESSION_PATH':sessions.name,'HOMECORE_DB_DSN':f'mysql:unix_socket={state}/mysql.sock;dbname={name};charset=utf8mb4','HOMECORE_DB_USER':'root'}
    # Upgrade a populated phase-1 schema, not just a blank database.
    sql(f'USE {name}; ' + (root/'backend/migrations/001_users_homes.sql').read_text())
    legacy_hash=subprocess.check_output(['php','-r',"echo password_hash('legacy-password-123', PASSWORD_DEFAULT);"],text=True)
    sql(f"INSERT INTO {name}.users (name,email,password_hash) VALUES ('Legacy','legacy@example.test','{legacy_hash}')")
    for _ in range(2): subprocess.run(['php',str(root/'backend/bin/migrate.php')],env=env,check=True)
    assert sql(f"SELECT password_hash FROM {name}.users WHERE email='legacy@example.test'").strip()==legacy_hash

    assert sql(f'SELECT COUNT(*) FROM {name}.schema_migrations').strip()==str(len(list((root/'backend/migrations').glob('*.sql'))))
    with socket.socket() as s: s.bind(('127.0.0.1',0)); port=s.getsockname()[1]
    base=f'http://127.0.0.1:{port}/api/v1'
    with tempfile.TemporaryFile() as log:
        server=subprocess.Popen(['php','-S',f'127.0.0.1:{port}','-t',str(root/'backend/public'),str(root/'backend/public/index.php')],env=env,stdout=log,stderr=log)
        for _ in range(100):
            try:
                with socket.create_connection(('127.0.0.1',port),.1): break
            except OSError: time.sleep(.05)
        legacy=Client(); legacy.request('/auth/csrf')
        legacy.request('/auth/login','POST',{'email':'legacy@example.test','password':'legacy-password-123'})
        assert legacy.request('/auth/me')['user']['username'] is None
        a,b,c=Client(),Client(),Client()
        a.request('/homes',expected=401)
        a.request('/auth/register','POST',{},403,False)
        owner=a.register('owner@example.test'); resident=b.register('resident@example.test'); c.register('stranger@example.test')
        assert any(cookie.has_nonstandard_attr('HttpOnly') and cookie.get_nonstandard_attr('SameSite')=='Lax' for cookie in a.jar)
        assert 'password_hash' not in a.request('/auth/me')['user']
        a.request('/auth/register','POST',{'name':'X','email':'owner@example.test','password':'correct-password-123'},409)
        a.request('/homes','POST',{'name':'Casa','timezone':'invalid'},422)
        hid=a.request('/homes','POST',{'name':'Casa','timezone':'America/Mazatlan'},201)['home']['id']
        assert len(a.request('/homes')['homes'])==1
        assert b.request('/homes')['homes']==[]
        c.request(f'/homes/{hid}',expected=404)
        c.request(f'/homes/{hid}','PATCH',{'name':'Intrusión'},404)
        rid=a.request(f'/homes/{hid}/rooms','POST',{'name':'Sala'},201)['room']['id']
        c.request(f'/rooms/{rid}','PATCH',{'name':'Intrusión'},404)
        a.request(f'/homes/{hid}/members','POST',{'email':'resident@example.test','role':'owner'},422)
        a.request(f'/homes/{hid}/members','POST',{'email':'resident@example.test','role':'resident'},201)
        assert len(b.request(f'/homes/{hid}/rooms')['rooms'])==1
        b.request(f'/homes/{hid}/rooms','POST',{'name':'No permitido'},403)
        b.request(f'/rooms/{rid}','PATCH',{'name':'No permitido'},403)
        b.request(f'/homes/{hid}/members',expected=403)
        a.request(f'/homes/{hid}/members/{owner["id"]}','DELETE',{},422)
        a.request(f'/homes/{hid}/members/{resident["id"]}','PATCH',{'role':'guest'})
        assert b.request(f'/homes/{hid}')['home']['role']=='guest'
        a.request(f'/homes/{hid}/members/{resident["id"]}','DELETE',{})
        b.request(f'/homes/{hid}',expected=404)
        a.request(f'/rooms/{rid}','PATCH',{'name':'Cocina'})
        a.request(f'/homes/{hid}','PATCH',{'name':'Mi Casa'})
        assert a.request(f'/homes/{hid}/rooms')['rooms'][0]['name']=='Cocina'
        a.request('/auth/logout','POST',{})
        a.request('/auth/me',expected=401)
        a.request('/auth/csrf')
        a.request('/auth/login','POST',{'email':'owner@example.test','password':'incorrect-password'},401)
        before=[cookie.value for cookie in a.jar]
        a.request('/auth/login','POST',{'email':'OWNER@example.test','password':'correct-password-123'})
        assert before!=[cookie.value for cookie in a.jar]
        assert a.request('/auth/me')['user']['id']==owner['id']
        stored=sql(f'SELECT password_hash FROM {name}.users LIMIT 1').strip()
        assert stored.startswith('$2y$') and stored!='correct-password-123'
        # Existing email accounts can choose a unique username without losing email login.
        a.request('/auth/me','PATCH',{'username':'Oscar.Casa'})
        assert a.request('/auth/me')['user']['username']=='oscar.casa'
        b.request('/auth/me','PATCH',{'username':'OSCAR.CASA'},409)
        a.request('/auth/me','PATCH',{'username':'not an id'},422)
        noemail=Client(); noemail.request('/auth/csrf')
        noemail.request('/auth/register','POST',{'name':'Adulto','username':'adulto.unico','password':'correct-password-123'},201)
        assert noemail.request('/auth/me')['user']['email'] is None
        a.request(f'/homes/{hid}/members','POST',{'identifier':'ADULTO.UNICO','role':'resident'},201)
        second=c.request('/homes','POST',{'name':'Otro hogar','timezone':'UTC'},201)['home']['id']
        foreign=c.request(f'/homes/{second}/rooms','POST',{'name':'Privada'},201)['room']['id']
        childData={'name':'Niño','username':'peque.casa','password':'child-password-123','enabled':True,'room_ids':[rid]}
        child=a.request(f'/homes/{hid}/children','POST',childData,201)['user']
        assert child['email'] is None and child['account_type']=='child'
        a.request(f'/homes/{hid}/children','POST',childData,409)
        bad={**childData,'username':'invalid.room','room_ids':[foreign]}
        a.request(f'/homes/{hid}/children','POST',bad,422)
        b.request(f'/homes/{hid}/children',expected=404)
        noemail.request(f'/homes/{hid}/children',expected=403)
        kid=Client(); kid.request('/auth/csrf')
        kid.request('/auth/login','POST',{'identifier':'PEQUE.CASA','password':'child-password-123'})
        assert [r['id'] for r in kid.request(f'/homes/{hid}/rooms')['rooms']]==[rid]
        kid.request(f'/homes/{second}/rooms',expected=404)
        kid.request('/homes','POST',{'name':'Escape','timezone':'UTC'},403)
        kid.request('/auth/me','PATCH',{'username':'escape'},403)
        kid.request(f'/homes/{hid}/children',expected=403)
        kid.request(f'/homes/{hid}/members',expected=403)
        kid.request(f'/rooms/{rid}','PATCH',{'name':'Escape'},403)
        a.request(f'/homes/{hid}/members/{child["id"]}','PATCH',{'role':'resident'},422)
        c.request(f'/homes/{second}/members','POST',{'identifier':'peque.casa','role':'resident'},422)
        c.request(f'/homes/{second}/children/{child["id"]}','PATCH',{**childData,'room_ids':[]},404)
        edit={**childData,'password':'','room_ids':[]}
        a.request(f'/homes/{hid}/children/{child["id"]}','PATCH',edit)
        kid.request('/auth/me',expected=401)
        kid.request('/auth/csrf'); kid.request('/auth/login','POST',{'identifier':'peque.casa','password':'child-password-123'})
        assert kid.request(f'/homes/{hid}/rooms')['rooms']==[]
        a.request(f'/homes/{hid}/children/{child["id"]}','PATCH',{**edit,'enabled':False})
        kid.request('/auth/me',expected=401)
        kid.request('/auth/csrf'); kid.request('/auth/login','POST',{'identifier':'peque.casa','password':'child-password-123'},403)
        a.request(f'/homes/{hid}/children/{child["id"]}','PATCH',{**edit,'password':'new-child-password','enabled':True})
        kid.request('/auth/login','POST',{'identifier':'peque.casa','password':'child-password-123'},401)
        kid.request('/auth/login','POST',{'identifier':'peque.casa','password':'new-child-password'})
        assert kid.request('/auth/me')['user']['id']==child['id']
        a.request('/auth/logout','POST',{}); a.request('/auth/csrf')
        a.request('/auth/login','POST',{'identifier':'oscar.casa','password':'correct-password-123'})
        assert a.request('/auth/me')['user']['id']==owner['id']
        print('PASS: usuarios únicos sin correo, perfiles infantiles, permisos por habitación, pausa, restablecimiento y revocación de sesiones')
        sql(f'UPDATE {name}.auth_attempts SET attempts=30')
        c.request('/auth/login','POST',{'email':'owner@example.test','password':'incorrect-password'},429)
        print('PASS: migraciones repetibles, registro, login, logout, cookies, CSRF, roles, aislamiento, revocación, validación y límite de intentos')
finally:
    if server: server.terminate(); server.wait(timeout=5)
    sql(f'DROP DATABASE {name}')
    sessions.cleanup()
