<?php
declare(strict_types=1);

namespace App\Controllers;

use App\Core\Controller;
use App\Core\Response;
use App\Core\Auth;
use App\Models\UserModel;
use App\Models\TenantModel;
use App\Models\RoleModel;
use App\Config\Database;
use App\Helpers\MailHelper;
use Exception;

/**
 * Authentication Controller
 */
class AuthController extends Controller
{
    private UserModel $userModel;
    private TenantModel $tenantModel;
    private RoleModel $roleModel;

    public function __construct()
    {
        $this->userModel = new UserModel();
        $this->tenantModel = new TenantModel();
        $this->roleModel = new RoleModel();
    }

    /**
     * Register a new Tenant and Admin User
     */
    public function register(): void
    {
        $data = $this->getJsonBody();
        $this->validate($data, [
            'company_name' => 'required|min:3',
            'email' => 'required|email',
            'password' => 'required|min:8',
            'phone' => 'required'
        ]);

        $db = Database::getInstance();
        $db->beginTransaction();

        try {
            // Check if user exists
            if ($this->userModel->findByEmail($data['email'])) {
                Response::error('Email already registered', ['email' => ['Address already in use']], 422);
            }

            // 1. Create Tenant
            $tenantId = $this->tenantModel->create([
                'company_name' => $data['company_name'],
                'email' => $data['email'],
                'phone' => $data['phone'],
                'gstin' => $data['gstin'] ?? null,
                'status' => 'trial',
                'trial_ends_at' => date('Y-m-d', strtotime('+14 days'))
            ]);

            // 2. Create Default Roles
            $roles = [
                ['name' => 'Admin', 'permissions_json' => json_encode(['*.*']), 'is_default' => 1],
                ['name' => 'Manager', 'permissions_json' => json_encode(['invoices.*', 'customers.*', 'products.*', 'inventory.*', 'reports.view']), 'is_default' => 0],
                ['name' => 'Accountant', 'permissions_json' => json_encode(['invoices.view', 'invoices.create', 'invoices.edit', 'payments.*', 'reports.*', 'expenses.*']), 'is_default' => 0],
                ['name' => 'Viewer', 'permissions_json' => json_encode(['*.view']), 'is_default' => 0]
            ];

            $adminRoleId = null;
            foreach ($roles as $roleData) {
                $roleData['tenant_id'] = $tenantId;
                $id = $this->roleModel->create($roleData);
                if ($roleData['name'] === 'Admin')
                    $adminRoleId = $id;
            }

            // 3. Create Admin User
            $userId = $this->userModel->create([
                'tenant_id' => $tenantId,
                'name' => $data['name'] ?? 'Administrator',
                'email' => $data['email'],
                'password_hash' => password_hash($data['password'], PASSWORD_BCRYPT),
                'role_id' => $adminRoleId,
                'is_active' => 1
            ]);

            $db->commit();

            $tokens = $this->issueTokens($userId, $tenantId, 'Admin', ['*.*']);
            $user = $this->userModel->find($userId);
            $tenant = $this->tenantModel->find($tenantId);

            Response::success([
                'access_token' => $tokens['access_token'],
                'refresh_token' => $tokens['refresh_token'],
                'user' => [
                    'id' => $user['id'],
                    'name' => $user['name'],
                    'email' => $user['email']
                ],
                'tenant' => $tenant
            ], 'Registration successful', 201);

        } catch (Exception $e) {
            $db->rollBack();
            Response::error('Registration failed: ' . $e->getMessage(), [], 500);
        }
    }

    /**
     * User Login
     */
    public function login(): void
    {
        $data = $this->getJsonBody();
        $this->validate($data, [
            'email' => 'required|email',
            'password' => 'required'
        ]);

        $user = $this->userModel->findByEmail($data['email']);

        if (!$user || !password_verify($data['password'], $user['password_hash'])) {
            Response::error('Invalid credentials', [], 401);
        }

        if (!$user['is_active']) {
            Response::error('Account is deactivated', [], 403);
        }

        // Get Role and Tenant
        $role = $this->roleModel->find($user['role_id']);
        $tenant = $this->tenantModel->find($user['tenant_id']);
        $permissions = json_decode($role['permissions_json'] ?? '[]', true);

        // Update last login
        $this->userModel->update($user['id'], ['last_login' => date('Y-m-d H:i:s')]);

        $tokens = $this->issueTokens($user['id'], $user['tenant_id'], $role['name'] ?? 'User', $permissions);

        Response::success([
            'access_token' => $tokens['access_token'],
            'refresh_token' => $tokens['refresh_token'],
            'user' => [
                'id' => $user['id'],
                'name' => $user['name'],
                'email' => $user['email']
            ],
            'tenant' => $tenant,
            'permissions' => json_decode($role['permissions_json'], true)
        ], 'Login successful');
    }

    /**
     * Refresh Access Token
     */
    public function refresh(): void
    {
        $data = $this->getJsonBody();
        $token = $data['refresh_token'] ?? '';

        if (!$token) {
            Response::error('Refresh token required', [], 400);
        }

        $userId = Auth::rotateRefreshToken($token);

        if (!$userId) {
            Response::error('Invalid or expired refresh token', [], 401);
        }

        // Fetch user and tenant to re-issue tokens
        $user = $this->userModel->find($userId);
        if (!$user || !$user['is_active']) {
            Response::error('User account is invalid or deactivated', [], 403);
        }
        
        $role = $this->roleModel->find($user['role_id']);
        $permissions = json_decode($role['permissions_json'] ?? '[]', true);
        $tokens = $this->issueTokens($user['id'], $user['tenant_id'], $role['name'], $permissions);

        Response::success([
            'access_token' => $tokens['access_token'],
            'refresh_token' => $tokens['refresh_token']
        ], 'Token refreshed successfully');
    }

    /**
     * Logout and revoke tokens
     */
    public function logout(): void
    {
        $data = $this->getJsonBody();
        $token = $data['refresh_token'] ?? '';

        if ($token) {
            Auth::revokeRefreshToken($token);
        }

        Response::success([], 'Logged out successfully');
    }

    /**
     * Get Current User Context
     */
    public function me(): void
    {
        $userId = $_REQUEST['user']['sub'] ?? null;
        if (!$userId) {
            Response::error('Invalid user context', [], 401);
        }

        $user = $this->userModel->find((int) $userId);
        if (!$user) {
            Response::error('User session invalid or account not found', [], 401);
        }

        $tenant = $this->tenantModel->find((int) $user['tenant_id']);
        if (!$tenant) {
            Response::error('Associated tenant context not found', [], 403);
        }

        $role = $this->roleModel->find((int) $user['role_id']);
        if (!$role) {
            Response::error('User role context not found', [], 403);
        }

        // Inject resolved role name for UI
        $user['role'] = $role['name'] ?? 'User';

        Response::success([
            'user' => $user,
            'tenant' => $tenant,
            'permissions' => json_decode($role['permissions_json'] ?? '[]', true)
        ]);
    }

    /**
     * Helper to issue both tokens
     */
    private function issueTokens(int $userId, int $tenantId, string $role, array $permissions = []): array
    {
        return [
            'access_token' => Auth::generateAccessToken($userId, $tenantId, $role, $permissions),
            'refresh_token' => Auth::generateRefreshToken($userId)
        ];
    }

    /**
     * Forgot Password Flow (Send Email)
     */
    public function forgotPassword(): void
    {
        $data = $this->getJsonBody();
        $this->validate($data, [
            'email' => 'required|email'
        ]);

        $user = $this->userModel->findByEmail($data['email']);
        if (!$user) {
            // Always return success to prevent user enumeration
            Response::success([], 'If the email exists, a password reset link has been sent.');
        }

        // Generate a 64 character hex token
        $token = bin2hex(random_bytes(32));
        $expires = date('Y-m-d H:i:s', strtotime('+1 hour'));

        // Save token in DB
        $this->userModel->update($user['id'], [
            'reset_token' => $token,
            'reset_expires' => $expires
        ]);

        // Build reset link
        $frontendUrl = rtrim(FRONTEND_URL, '/');
        $resetLink = $frontendUrl . '/#/reset-password?token=' . $token;

        // Email subject & body
        $subject = 'Password Reset Request';
        $body = "<h2>Password Reset</h2>
                 <p>You requested a password reset. Click the link below to reset it:</p>
                 <br>
                 <a href='{$resetLink}' style='padding:10px 20px; background:#10B981; color:#fff; text-decoration:none; border-radius:5px;'>Reset Password</a>
                 <br><br>
                 <p>If you did not request this, please ignore this email. This link expires in 1 hour.</p>";

        // Send Email
        try {
            MailHelper::send($user['email'], $subject, $body);
        } catch (Exception $e) {
            // Log silently and don't expose SMTP internal crash to client
            error_log('SMTP Handshake Error: ' . $e->getMessage());
        }

        // Always return success to obfuscate account discovery and allow UI transition
        Response::success([], 'If the email exists, a password reset link has been sent.');
    }

    /**
     * Reset Password (with Token)
     */
    public function resetPassword(): void
    {
        $data = $this->getJsonBody();
        $this->validate($data, [
            'token' => 'required',
            'password' => 'required|min:8'
        ]);

        // Find user by reset_token
        $db = Database::getInstance();
        $stmt = $db->prepare('SELECT id, reset_expires FROM users WHERE reset_token = :token LIMIT 1');
        $stmt->execute(['token' => $data['token']]);
        $user = $stmt->fetch();

        if (!$user) {
            Response::error('Invalid or expired reset token.', [], 400);
        }

        // Check expiration
        if (strtotime($user['reset_expires']) < time()) {
            Response::error('Reset token has expired.', [], 400);
        }

        // Update password and clear token
        $this->userModel->update($user['id'], [
            'password_hash' => password_hash($data['password'], PASSWORD_BCRYPT),
            'reset_token' => null,
            'reset_expires' => null
        ]);

        Response::success([], 'Password has been successfully reset.');
    }
}
