import { Request, Response } from 'express';
import crypto from 'crypto';
import {
  createOAuthClient,
  findOAuthClientById,
  updateOAuthClient,
  deleteOAuthClient,
} from '../models/OAuth.js';
import { IOAuthClient } from '../types/index.js';
import { loadSettings } from '../config/index.js';

// Store registration access tokens (in production, use database)
const registrationTokens = new Map<string, { clientId: string; createdAt: Date }>();

/**
 * Generate registration access token
 */
const generateRegistrationToken = (clientId: string): string => {
  const token = crypto.randomBytes(32).toString('hex');
  registrationTokens.set(token, {
    clientId,
    createdAt: new Date(),
  });
  return token;
};

/**
 * Verify registration access token
 */
const verifyRegistrationToken = (token: string): string | null => {
  const data = registrationTokens.get(token);
  if (!data) {
    return null;
  }

  // Token expires after 30 days
  const expiresAt = new Date(data.createdAt.getTime() + 30 * 24 * 60 * 60 * 1000);
  if (new Date() > expiresAt) {
    registrationTokens.delete(token);
    return null;
  }

  return data.clientId;
};

/**
 * POST /oauth/register
 * RFC 7591 Dynamic Client Registration
 * Public endpoint for registering new OAuth clients
 */
export const registerClient = async (req: Request, res: Response): Promise<void> => {
  try {
    const settings = loadSettings();
    const oauthConfig = settings.systemConfig?.oauthServer;

    // Check if dynamic registration is enabled
    if (!oauthConfig?.dynamicRegistration?.enabled) {
      res.status(403).json({
        error: 'invalid_request',
        error_description: 'Dynamic client registration is not enabled',
      });
      return;
    }

    // Validate required fields
    const {
      redirect_uris,
      client_name,
      grant_types,
      response_types,
      scope,
      token_endpoint_auth_method,
      application_type,
      contacts,
      logo_uri,
      client_uri,
      policy_uri,
      tos_uri,
      jwks_uri,
      jwks,
    } = req.body;

    // redirect_uris is required
    if (!redirect_uris || !Array.isArray(redirect_uris) || redirect_uris.length === 0) {
      res.status(400).json({
        error: 'invalid_redirect_uri',
        error_description: 'redirect_uris is required and must be a non-empty array',
      });
      return;
    }

    // Validate redirect URIs
    for (const uri of redirect_uris) {
      try {
        const url = new URL(uri);
        // For security, only allow https (except localhost for development)
        if (
          url.protocol !== 'https:' &&
          !url.hostname.match(/^(localhost|127\.0\.0\.1|\[::1\])$/)
        ) {
          res.status(400).json({
            error: 'invalid_redirect_uri',
            error_description: `Redirect URI must use HTTPS: ${uri}`,
          });
          return;
        }
      } catch (e) {
        res.status(400).json({
          error: 'invalid_redirect_uri',
          error_description: `Invalid redirect URI: ${uri}`,
        });
        return;
      }
    }

    // Generate client credentials
    const clientId = crypto.randomBytes(16).toString('hex');

    // Determine if client secret is needed based on token_endpoint_auth_method
    const authMethod = token_endpoint_auth_method || 'client_secret_basic';
    const needsSecret = authMethod !== 'none';
    const clientSecret = needsSecret ? crypto.randomBytes(32).toString('hex') : undefined;

    // Default grant types
    const defaultGrantTypes = ['authorization_code', 'refresh_token'];
    const clientGrantTypes = grant_types || defaultGrantTypes;

    // Validate grant types
    const allowedGrantTypes = oauthConfig.dynamicRegistration.allowedGrantTypes || [
      'authorization_code',
      'refresh_token',
    ];
    for (const grantType of clientGrantTypes) {
      if (!allowedGrantTypes.includes(grantType)) {
        res.status(400).json({
          error: 'invalid_client_metadata',
          error_description: `Grant type not allowed: ${grantType}`,
        });
        return;
      }
    }

    // Validate scopes
    const requestedScopes = scope ? scope.split(' ') : ['read', 'write'];
    const allowedScopes = oauthConfig.allowedScopes || ['read', 'write'];
    for (const requestedScope of requestedScopes) {
      if (!allowedScopes.includes(requestedScope)) {
        res.status(400).json({
          error: 'invalid_client_metadata',
          error_description: `Scope not allowed: ${requestedScope}`,
        });
        return;
      }
    }

    // Generate registration access token
    const registrationAccessToken = generateRegistrationToken(clientId);
    const baseUrl =
      settings.systemConfig?.install?.baseUrl || `${req.protocol}://${req.get('host')}`;
    const registrationClientUri = `${baseUrl}/oauth/register/${clientId}`;

    // Create OAuth client
    const client: IOAuthClient = {
      clientId,
      clientSecret,
      name: client_name || 'Dynamically Registered Client',
      redirectUris: redirect_uris,
      grants: clientGrantTypes,
      scopes: requestedScopes,
      owner: 'dynamic-registration',
      // Store additional metadata
      metadata: {
        application_type: application_type || 'web',
        contacts,
        logo_uri,
        client_uri,
        policy_uri,
        tos_uri,
        jwks_uri,
        jwks,
        token_endpoint_auth_method: authMethod,
        response_types: response_types || ['code'],
      },
    };

    const createdClient = await createOAuthClient(client);

    // Build response according to RFC 7591
    const response: any = {
      client_id: createdClient.clientId,
      client_name: createdClient.name,
      redirect_uris: createdClient.redirectUris,
      grant_types: createdClient.grants,
      response_types: client.metadata?.response_types || ['code'],
      scope: (createdClient.scopes || []).join(' '),
      token_endpoint_auth_method: authMethod,
      registration_access_token: registrationAccessToken,
      registration_client_uri: registrationClientUri,
      client_id_issued_at: Math.floor(Date.now() / 1000),
    };

    // Include client secret if generated
    if (clientSecret) {
      response.client_secret = clientSecret;
      response.client_secret_expires_at = 0; // 0 means it doesn't expire
    }

    // Include optional metadata
    if (application_type) response.application_type = application_type;
    if (contacts) response.contacts = contacts;
    if (logo_uri) response.logo_uri = logo_uri;
    if (client_uri) response.client_uri = client_uri;
    if (policy_uri) response.policy_uri = policy_uri;
    if (tos_uri) response.tos_uri = tos_uri;
    if (jwks_uri) response.jwks_uri = jwks_uri;
    if (jwks) response.jwks = jwks;

    res.status(201).json(response);
  } catch (error) {
    console.error('Dynamic client registration error:', error);

    if (error instanceof Error && error.message.includes('already exists')) {
      res.status(400).json({
        error: 'invalid_client_metadata',
        error_description: 'Client with this ID already exists',
      });
    } else {
      res.status(500).json({
        error: 'server_error',
        error_description: 'Failed to register client',
      });
    }
  }
};

/**
 * GET /oauth/register/:clientId
 * RFC 7591 Client Configuration Endpoint
 * Read client configuration
 */
export const getClientConfiguration = async (req: Request, res: Response): Promise<void> => {
  try {
    const { clientId } = req.params;
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      res.status(401).json({
        error: 'invalid_token',
        error_description: 'Registration access token required',
      });
      return;
    }

    const token = authHeader.substring(7);
    const tokenClientId = verifyRegistrationToken(token);

    if (!tokenClientId || tokenClientId !== clientId) {
      res.status(401).json({
        error: 'invalid_token',
        error_description: 'Invalid or expired registration access token',
      });
      return;
    }

    const client = await findOAuthClientById(clientId);
    if (!client) {
      res.status(404).json({
        error: 'invalid_client',
        error_description: 'Client not found',
      });
      return;
    }

    // Build response
    const response: any = {
      client_id: client.clientId,
      client_name: client.name,
      redirect_uris: client.redirectUris,
      grant_types: client.grants,
      response_types: client.metadata?.response_types || ['code'],
      scope: (client.scopes || []).join(' '),
      token_endpoint_auth_method:
        client.metadata?.token_endpoint_auth_method || 'client_secret_basic',
    };

    // Include optional metadata
    if (client.metadata) {
      if (client.metadata.application_type)
        response.application_type = client.metadata.application_type;
      if (client.metadata.contacts) response.contacts = client.metadata.contacts;
      if (client.metadata.logo_uri) response.logo_uri = client.metadata.logo_uri;
      if (client.metadata.client_uri) response.client_uri = client.metadata.client_uri;
      if (client.metadata.policy_uri) response.policy_uri = client.metadata.policy_uri;
      if (client.metadata.tos_uri) response.tos_uri = client.metadata.tos_uri;
      if (client.metadata.jwks_uri) response.jwks_uri = client.metadata.jwks_uri;
      if (client.metadata.jwks) response.jwks = client.metadata.jwks;
    }

    res.json(response);
  } catch (error) {
    console.error('Get client configuration error:', error);
    res.status(500).json({
      error: 'server_error',
      error_description: 'Failed to retrieve client configuration',
    });
  }
};

/**
 * PUT /oauth/register/:clientId
 * RFC 7591 Client Update Endpoint
 * Update client configuration
 */
export const updateClientConfiguration = async (req: Request, res: Response): Promise<void> => {
  try {
    const { clientId } = req.params;
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      res.status(401).json({
        error: 'invalid_token',
        error_description: 'Registration access token required',
      });
      return;
    }

    const token = authHeader.substring(7);
    const tokenClientId = verifyRegistrationToken(token);

    if (!tokenClientId || tokenClientId !== clientId) {
      res.status(401).json({
        error: 'invalid_token',
        error_description: 'Invalid or expired registration access token',
      });
      return;
    }

    const client = await findOAuthClientById(clientId);
    if (!client) {
      res.status(404).json({
        error: 'invalid_client',
        error_description: 'Client not found',
      });
      return;
    }

    const {
      redirect_uris,
      client_name,
      grant_types,
      scope,
      contacts,
      logo_uri,
      client_uri,
      policy_uri,
      tos_uri,
    } = req.body;

    const settings = loadSettings();
    const oauthConfig = settings.systemConfig?.oauthServer;

    // Validate redirect URIs if provided
    if (redirect_uris) {
      if (!Array.isArray(redirect_uris) || redirect_uris.length === 0) {
        res.status(400).json({
          error: 'invalid_redirect_uri',
          error_description: 'redirect_uris must be a non-empty array',
        });
        return;
      }

      for (const uri of redirect_uris) {
        try {
          const url = new URL(uri);
          if (
            url.protocol !== 'https:' &&
            !url.hostname.match(/^(localhost|127\.0\.0\.1|\[::1\])$/)
          ) {
            res.status(400).json({
              error: 'invalid_redirect_uri',
              error_description: `Redirect URI must use HTTPS: ${uri}`,
            });
            return;
          }
        } catch (e) {
          res.status(400).json({
            error: 'invalid_redirect_uri',
            error_description: `Invalid redirect URI: ${uri}`,
          });
          return;
        }
      }
    }

    // Validate grant types if provided
    if (grant_types) {
      const allowedGrantTypes = oauthConfig?.dynamicRegistration?.allowedGrantTypes || [
        'authorization_code',
        'refresh_token',
      ];
      for (const grantType of grant_types) {
        if (!allowedGrantTypes.includes(grantType)) {
          res.status(400).json({
            error: 'invalid_client_metadata',
            error_description: `Grant type not allowed: ${grantType}`,
          });
          return;
        }
      }
    }

    // Validate scopes if provided
    if (scope) {
      const requestedScopes = scope.split(' ');
      const allowedScopes = oauthConfig?.allowedScopes || ['read', 'write'];
      for (const requestedScope of requestedScopes) {
        if (!allowedScopes.includes(requestedScope)) {
          res.status(400).json({
            error: 'invalid_client_metadata',
            error_description: `Scope not allowed: ${requestedScope}`,
          });
          return;
        }
      }
    }

    // Build updates
    const updates: Partial<IOAuthClient> = {};
    if (client_name) updates.name = client_name;
    if (redirect_uris) updates.redirectUris = redirect_uris;
    if (grant_types) updates.grants = grant_types;
    if (scope) updates.scopes = scope.split(' ');

    // Update metadata
    if (client.metadata || contacts || logo_uri || client_uri || policy_uri || tos_uri) {
      updates.metadata = {
        ...client.metadata,
        contacts,
        logo_uri,
        client_uri,
        policy_uri,
        tos_uri,
      };
    }

    const updatedClient = await updateOAuthClient(clientId, updates);

    if (!updatedClient) {
      res.status(500).json({
        error: 'server_error',
        error_description: 'Failed to update client',
      });
      return;
    }

    // Build response
    const response: any = {
      client_id: updatedClient.clientId,
      client_name: updatedClient.name,
      redirect_uris: updatedClient.redirectUris,
      grant_types: updatedClient.grants,
      response_types: updatedClient.metadata?.response_types || ['code'],
      scope: (updatedClient.scopes || []).join(' '),
      token_endpoint_auth_method:
        updatedClient.metadata?.token_endpoint_auth_method || 'client_secret_basic',
    };

    // Include optional metadata
    if (updatedClient.metadata) {
      if (updatedClient.metadata.application_type)
        response.application_type = updatedClient.metadata.application_type;
      if (updatedClient.metadata.contacts) response.contacts = updatedClient.metadata.contacts;
      if (updatedClient.metadata.logo_uri) response.logo_uri = updatedClient.metadata.logo_uri;
      if (updatedClient.metadata.client_uri)
        response.client_uri = updatedClient.metadata.client_uri;
      if (updatedClient.metadata.policy_uri)
        response.policy_uri = updatedClient.metadata.policy_uri;
      if (updatedClient.metadata.tos_uri) response.tos_uri = updatedClient.metadata.tos_uri;
      if (updatedClient.metadata.jwks_uri) response.jwks_uri = updatedClient.metadata.jwks_uri;
      if (updatedClient.metadata.jwks) response.jwks = updatedClient.metadata.jwks;
    }

    res.json(response);
  } catch (error) {
    console.error('Update client configuration error:', error);
    res.status(500).json({
      error: 'server_error',
      error_description: 'Failed to update client configuration',
    });
  }
};

/**
 * DELETE /oauth/register/:clientId
 * RFC 7591 Client Delete Endpoint
 * Delete client registration
 */
export const deleteClientRegistration = async (req: Request, res: Response): Promise<void> => {
  try {
    const { clientId } = req.params;
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      res.status(401).json({
        error: 'invalid_token',
        error_description: 'Registration access token required',
      });
      return;
    }

    const token = authHeader.substring(7);
    const tokenClientId = verifyRegistrationToken(token);

    if (!tokenClientId || tokenClientId !== clientId) {
      res.status(401).json({
        error: 'invalid_token',
        error_description: 'Invalid or expired registration access token',
      });
      return;
    }

    const deleted = await deleteOAuthClient(clientId);

    if (!deleted) {
      res.status(404).json({
        error: 'invalid_client',
        error_description: 'Client not found',
      });
      return;
    }

    // Clean up registration token
    registrationTokens.delete(token);

    res.status(204).send();
  } catch (error) {
    console.error('Delete client registration error:', error);
    res.status(500).json({
      error: 'server_error',
      error_description: 'Failed to delete client registration',
    });
  }
};
