import requests
import logging
import re
from odoo import models, api, _, SUPERUSER_ID
from odoo.http import request
from odoo.exceptions import AccessDenied

_logger = logging.getLogger(__name__)


class ResUsers(models.Model):
    _inherit = "res.users"

    @api.model
    def auth_oauth(self, provider, params):
        """Override to handle authorization code flow"""
        
        # Check if we received a code instead of access_token
        if "code" in params and "access_token" not in params:
            _logger.info(f"OAuth: Received authorization code, exchanging for token")
            
            try:
                oauth_provider = self.env["auth.oauth.provider"].browse(provider)
                
                match = re.match(r"(https?://[^/]+)", oauth_provider.validation_endpoint)
                if not match:
                    _logger.error("Cannot extract issuer from validation_endpoint")
                    raise Exception("invalid_request")
                
                issuer = match.group(1)
                token_endpoint = f"{issuer}/application/o/token/"
                
                if request:
                    redirect_uri = request.httprequest.url_root.rstrip("/") + "/auth_oauth/signin"
                else:
                    redirect_uri = "https://odoo.ai-impress.com/auth_oauth/signin"
                
                token_data = {
                    "grant_type": "authorization_code",
                    "code": params["code"],
                    "redirect_uri": redirect_uri,
                    "client_id": oauth_provider.client_id,
                    "client_secret": oauth_provider.client_secret,
                }
                
                _logger.info(f"Exchanging code at {token_endpoint}")
                
                response = requests.post(token_endpoint, data=token_data, timeout=10)
                
                if response.status_code != 200:
                    _logger.error(f"Token exchange failed: {response.text}")
                    raise Exception("access_denied")
                
                token_response = response.json()
                access_token = token_response.get("access_token")
                
                if not access_token:
                    _logger.error("No access token in response")
                    raise Exception("access_denied")
                
                _logger.info(f"Successfully obtained access token")
                
                params = dict(params)
                params["access_token"] = access_token
                params.pop("code", None)
                
            except Exception as e:
                _logger.error(f"Failed to exchange code for token: {e}", exc_info=True)
                raise
        
        # Call parent with updated params
        return super().auth_oauth(provider, params)
    
    @api.model
    def _auth_oauth_validate(self, provider, access_token):
        """Override to get user info from Authentik"""
        oauth_provider = self.env["auth.oauth.provider"].browse(provider)
        
        validation_endpoint = oauth_provider.validation_endpoint
        if not validation_endpoint:
            return super()._auth_oauth_validate(provider, access_token)
        
        headers = {"Authorization": f"Bearer {access_token}"}
        
        try:
            response = requests.get(validation_endpoint, headers=headers, timeout=10)
            response.raise_for_status()
            validation = response.json()
            
            # Add user_id field for compatibility
            validation["user_id"] = validation.get("sub")
            
            _logger.info(f"OAuth userinfo: email={validation.get('email')}, sub={validation.get('sub')}")
            
            return validation
            
        except Exception as e:
            _logger.error(f"OAuth validation failed: {e}")
            raise AccessDenied(_("OAuth: Cannot get user info"))
    
    @api.model
    def _auth_oauth_signin(self, provider, validation, params):
        """Override to auto-create internal users"""
        
        oauth_uid = validation.get("user_id")
        email = validation.get("email")
        name = validation.get("name") or validation.get("preferred_username") or email
        
        if not oauth_uid:
            raise AccessDenied(_("OAuth: Missing user ID"))
        
        _logger.info(f"OAuth signin: Looking for user with oauth_uid={oauth_uid}, email={email}")
        
        # Try to find existing OAuth user
        oauth_user = self.with_user(SUPERUSER_ID).search([
            ("oauth_uid", "=", oauth_uid),
            ("oauth_provider_id", "=", provider)
        ], limit=1)
        
        if oauth_user:
            _logger.info(f"OAuth: Found existing OAuth user: {oauth_user.login}")
            return oauth_user.login
        
        # Try to find user by email
        user = self.with_user(SUPERUSER_ID).search([("login", "=", email)], limit=1)
        
        if user:
            # Link existing user to OAuth
            _logger.info(f"OAuth: Linking existing user {user.login} to OAuth provider")
            user.write({
                "oauth_provider_id": provider,
                "oauth_uid": oauth_uid,
                "oauth_access_token": params.get("access_token"),
            })
            return user.login
        
        # Auto-create new internal user with minimal fields
        _logger.info(f"OAuth: Creating new internal user for {email}")

        try:
            # Step 1: Create partner first (name and email are stored in res_partner)
            self.env.cr.execute("""
                INSERT INTO res_partner (
                    name, email, company_id, active,
                    create_uid, create_date, write_uid, write_date
                ) VALUES (
                    %s, %s, %s, %s,
                    %s, NOW(), %s, NOW()
                )
                RETURNING id
            """, (
                name, email, 1, True,
                SUPERUSER_ID, SUPERUSER_ID
            ))

            partner_result = self.env.cr.fetchone()
            if not partner_result:
                raise Exception("Failed to create partner")

            partner_id = partner_result[0]
            self.env.cr.commit()

            # Step 2: Create user with reference to partner
            self.env.cr.execute("""
                INSERT INTO res_users (
                    login, partner_id, active, share, oauth_provider_id, oauth_uid, oauth_access_token,
                    create_uid, create_date, write_uid, write_date, company_id
                ) VALUES (
                    %s, %s, %s, %s, %s, %s, %s,
                    %s, NOW(), %s, NOW(), %s
                )
                RETURNING id
            """, (
                email, partner_id, True, False, provider, oauth_uid, params.get("access_token"),
                SUPERUSER_ID, SUPERUSER_ID, 1
            ))

            user_result = self.env.cr.fetchone()
            if not user_result:
                raise Exception("Failed to create user")

            user_id = user_result[0]
            self.env.cr.commit()

            # Step 3: Reload and verify
            new_user = self.with_user(SUPERUSER_ID).browse(user_id)

            _logger.info(f"OAuth: Successfully created internal user: {new_user.login} (id={new_user.id}, partner_id={new_user.partner_id.id})")
            return new_user.login

        except Exception as e:
            _logger.error(f"OAuth: Failed to create user: {e}", exc_info=True)
            try:
                self.env.cr.rollback()
            except:
                pass
            raise AccessDenied(_("OAuth: Could not create user"))
