/**
 * Authentication Service for Redirect Intelligence v2
 * 
 * Handles user authentication, password hashing, and JWT token management
 */

import argon2 from 'argon2';
import jwt from 'jsonwebtoken';
import { z } from 'zod';
import { prisma } from '../lib/prisma';
import { logger } from '../lib/logger';

// Input validation schemas
const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

const registerSchema = z.object({
  email: z.string().email(),
  name: z.string().min(2),
  password: z.string().min(8),
  organizationName: z.string().min(2).optional(),
});

export interface AuthUser {
  id: string;
  email: string;
  name: string;
  memberships: Array<{
    orgId: string;
    role: string;
    organization: {
      name: string;
      plan: string;
    };
  }>;
}

export interface AuthResult {
  user: AuthUser;
  token: string;
}

export class AuthService {
  private readonly JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret-change-in-production';
  private readonly JWT_EXPIRES_IN = '7d';

  /**
   * Hash a password using Argon2
   */
  async hashPassword(password: string): Promise<string> {
    try {
      return await argon2.hash(password, {
        type: argon2.argon2id,
        memoryCost: 2 ** 16, // 64 MB
        timeCost: 3,
        parallelism: 1,
      });
    } catch (error) {
      logger.error('Password hashing failed:', error);
      throw new Error('Password hashing failed');
    }
  }

  /**
   * Verify a password against its hash
   */
  async verifyPassword(hash: string, password: string): Promise<boolean> {
    try {
      return await argon2.verify(hash, password);
    } catch (error) {
      logger.error('Password verification failed:', error);
      return false;
    }
  }

  /**
   * Generate a JWT token for a user
   */
  generateToken(userId: string, email: string): string {
    return jwt.sign(
      { 
        userId, 
        email,
        iat: Math.floor(Date.now() / 1000),
      },
      this.JWT_SECRET,
      { expiresIn: this.JWT_EXPIRES_IN }
    );
  }

  /**
   * Verify and decode a JWT token
   */
  verifyToken(token: string): { userId: string; email: string } {
    try {
      const decoded = jwt.verify(token, this.JWT_SECRET) as {
        userId: string;
        email: string;
      };
      return decoded;
    } catch (error) {
      logger.error('Token verification failed:', error);
      throw new Error('Invalid token');
    }
  }

  /**
   * Login user with email and password
   */
  async login(data: z.infer<typeof loginSchema>): Promise<AuthResult> {
    const { email, password } = loginSchema.parse(data);
    
    logger.info(`Login attempt for email: ${email}`);

    // Find user with memberships and organizations
    const user = await prisma.user.findUnique({
      where: { email },
      include: {
        memberships: {
          include: { 
            organization: {
              select: {
                name: true,
                plan: true,
              }
            }
          }
        }
      }
    });

    if (!user) {
      logger.warn(`Login failed: User not found for email ${email}`);
      throw new Error('Invalid credentials');
    }

    // Verify password
    const isValidPassword = await this.verifyPassword(user.passwordHash, password);
    if (!isValidPassword) {
      logger.warn(`Login failed: Invalid password for email ${email}`);
      throw new Error('Invalid credentials');
    }

    // Update last login timestamp
    await prisma.user.update({
      where: { id: user.id },
      data: { lastLoginAt: new Date() }
    });

    // Generate token
    const token = this.generateToken(user.id, user.email);

    // Format user data for response
    const authUser: AuthUser = {
      id: user.id,
      email: user.email,
      name: user.name,
      memberships: user.memberships.map(m => ({
        orgId: m.orgId,
        role: m.role,
        organization: {
          name: m.organization.name,
          plan: m.organization.plan,
        }
      }))
    };

    logger.info(`Login successful for user: ${user.email}`);

    return { user: authUser, token };
  }

  /**
   * Register a new user
   */
  async register(data: z.infer<typeof registerSchema>): Promise<AuthUser> {
    const { email, name, password, organizationName } = registerSchema.parse(data);

    logger.info(`Registration attempt for email: ${email}`);

    // Check if user already exists
    const existingUser = await prisma.user.findUnique({
      where: { email }
    });

    if (existingUser) {
      logger.warn(`Registration failed: User already exists for email ${email}`);
      throw new Error('User already exists');
    }

    // Hash password
    const passwordHash = await this.hashPassword(password);

    // Create user and organization in a transaction
    const result = await prisma.$transaction(async (tx) => {
      // Create user
      const user = await tx.user.create({
        data: {
          email,
          name,
          passwordHash,
        }
      });

      // Create organization (or use default)
      const organization = await tx.organization.create({
        data: {
          name: organizationName || `${name}'s Organization`,
          plan: 'free',
        }
      });

      // Create membership
      await tx.orgMembership.create({
        data: {
          userId: user.id,
          orgId: organization.id,
          role: 'OWNER',
        }
      });

      // Create default project
      await tx.project.create({
        data: {
          name: 'Default Project',
          orgId: organization.id,
          settingsJson: {
            description: 'Default project for redirect tracking',
            defaultMethod: 'GET',
          },
        }
      });

      return { user, organization };
    });

    logger.info(`Registration successful for user: ${email}`);

    return {
      id: result.user.id,
      email: result.user.email,
      name: result.user.name,
      memberships: [{
        orgId: result.organization.id,
        role: 'OWNER',
        organization: {
          name: result.organization.name,
          plan: result.organization.plan,
        }
      }]
    };
  }

  /**
   * Get user by ID with memberships
   */
  async getUserById(userId: string): Promise<AuthUser | null> {
    const user = await prisma.user.findUnique({
      where: { id: userId },
      include: {
        memberships: {
          include: { 
            organization: {
              select: {
                name: true,
                plan: true,
              }
            }
          }
        }
      }
    });

    if (!user) {
      return null;
    }

    return {
      id: user.id,
      email: user.email,
      name: user.name,
      memberships: user.memberships.map(m => ({
        orgId: m.orgId,
        role: m.role,
        organization: {
          name: m.organization.name,
          plan: m.organization.plan,
        }
      }))
    };
  }

  /**
   * Check if user has access to organization
   */
  async hasOrgAccess(userId: string, orgId: string): Promise<boolean> {
    const membership = await prisma.orgMembership.findUnique({
      where: {
        orgId_userId: {
          orgId,
          userId,
        }
      }
    });

    return !!membership;
  }

  /**
   * Get user's role in organization
   */
  async getUserRole(userId: string, orgId: string): Promise<string | null> {
    const membership = await prisma.orgMembership.findUnique({
      where: {
        orgId_userId: {
          orgId,
          userId,
        }
      }
    });

    return membership?.role || null;
  }
}
